actionpack 8.0.0.beta1 → 8.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4392b60cf6c4af7ed943daf2c4e438440689b8f869a859510b1e0e5808204cf2
4
- data.tar.gz: ee9993b25b397af527f1730dc439a0425a81d4b03afb148e310dac8862003261
3
+ metadata.gz: cbf30070e8c7658bcda189e9c07184b10bfdeb1ae28ae9b7c6e354189e96eac6
4
+ data.tar.gz: c21576442cf2c2e3ef1cf7e36f5e412349b6f933c651cca641a9afe48229cd96
5
5
  SHA512:
6
- metadata.gz: 2063ad6ca3243b066226af6a1d1ad616d808c48a18f80df374d02b064ad4839a971e4eff804321621d910111829b0c1130b5a05f5a44afabb380ec9f7d5985b7
7
- data.tar.gz: f3f1e1ce84c47654f69cf3f3ada2afa48e412dac1b7a5abd10c4d0fc34b66b7574f2454410c323cfa26a000005da585815db8a27dc3f1e9c2a71a504b0f3021d
6
+ metadata.gz: 8d09731d99912ded6338f7a0fcc0d98706efbf4721f26d35edcede064e240d607f6ddc5ed43a979ebe3c8d6c1e9b90347d84725c6b504c91c79fb9821edca478
7
+ data.tar.gz: 5b981e0db05e7d35cda56797acbf513050fd099bb3778ec2247012e1a338dec2b1fe608a6653a4dbe54c621de63f576588b98b8d77b172f9456b78e2e9a7cd9b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,40 @@
1
+ ## Rails 8.0.0.rc1 (October 19, 2024) ##
2
+
3
+ * Remove `Rails.application.config.action_controller.allow_deprecated_parameters_hash_equality`.
4
+
5
+ *Rafael Mendonça França*
6
+
7
+ * Improve `ActionController::TestCase` to expose a binary encoded `request.body`.
8
+
9
+ The rack spec clearly states:
10
+
11
+ > The input stream is an IO-like object which contains the raw HTTP POST data.
12
+ > When applicable, its external encoding must be “ASCII-8BIT” and it must be opened in binary mode.
13
+
14
+ Until now its encoding was generally UTF-8, which doesn't accurately reflect production
15
+ behavior.
16
+
17
+ *Jean Boussier*
18
+
19
+ * Update `ActionController::AllowBrowser` to support passing method names to `:block`
20
+
21
+ ```ruby
22
+ class ApplicationController < ActionController::Base
23
+ allow_browser versions: :modern, block: :handle_outdated_browser
24
+
25
+ private
26
+ def handle_outdated_browser
27
+ render file: Rails.root.join("public/custom-error.html"), status: :not_acceptable
28
+ end
29
+ end
30
+ ```
31
+
32
+ *Sean Doyle*
33
+
34
+ * Raise an `ArgumentError` when invalid `:only` or `:except` options are passed into `#resource` and `#resources`.
35
+
36
+ *Joshua Young*
37
+
1
38
  ## Rails 8.0.0.beta1 (September 26, 2024) ##
2
39
 
3
40
  * Fix non-GET requests not updating cookies in `ActionController::TestCase`.
@@ -104,6 +104,7 @@ module AbstractController
104
104
  # Declare a controller method as a helper. For example, the following
105
105
  # makes the `current_user` and `logged_in?` controller methods available
106
106
  # to the view:
107
+ #
107
108
  # class ApplicationController < ActionController::Base
108
109
  # helper_method :current_user, :logged_in?
109
110
  #
@@ -118,6 +119,7 @@ module AbstractController
118
119
  # end
119
120
  #
120
121
  # In a view:
122
+ #
121
123
  # <% if logged_in? -%>Welcome, <%= current_user.name %><% end -%>
122
124
  #
123
125
  # #### Parameters
@@ -5,7 +5,6 @@
5
5
  require "abstract_controller/error"
6
6
  require "action_view"
7
7
  require "action_view/view_paths"
8
- require "set"
9
8
 
10
9
  module AbstractController
11
10
  class DoubleRenderError < Error
@@ -22,10 +22,10 @@ module ActionController
22
22
  # default_form_builder AdminFormBuilder
23
23
  # end
24
24
  #
25
- # Then in the view any form using `form_for` will be an instance of the
26
- # specified form builder:
25
+ # Then in the view any form using `form_with` or `form_for` will be an
26
+ # instance of the specified form builder:
27
27
  #
28
- # <%= form_for(@instance) do |builder| %>
28
+ # <%= form_with(model: @instance) do |builder| %>
29
29
  # <%= builder.special_field(:name) %>
30
30
  # <% end %>
31
31
  module FormBuilder
@@ -36,6 +36,16 @@ module ActionController # :nodoc:
36
36
  # end
37
37
  #
38
38
  # class ApplicationController < ActionController::Base
39
+ # # Allow only browsers natively supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has
40
+ # allow_browser versions: :modern, block: :handle_outdated_browser
41
+ #
42
+ # private
43
+ # def handle_outdated_browser
44
+ # render file: Rails.root.join("public/custom-error.html"), status: :not_acceptable
45
+ # end
46
+ # end
47
+ #
48
+ # class ApplicationController < ActionController::Base
39
49
  # # All versions of Chrome and Opera will be allowed, but no versions of "internet explorer" (ie). Safari needs to be 16.4+ and Firefox 121+.
40
50
  # allow_browser versions: { safari: 16.4, firefox: 121, ie: false }
41
51
  # end
@@ -55,12 +65,12 @@ module ActionController # :nodoc:
55
65
 
56
66
  if BrowserBlocker.new(request, versions: versions).blocked?
57
67
  ActiveSupport::Notifications.instrument("browser_block.action_controller", request: request, versions: versions) do
58
- instance_exec(&block)
68
+ block.is_a?(Symbol) ? send(block) : instance_exec(&block)
59
69
  end
60
70
  end
61
71
  end
62
72
 
63
- class BrowserBlocker
73
+ class BrowserBlocker # :nodoc:
64
74
  SETS = {
65
75
  modern: { safari: 17.2, chrome: 120, firefox: 121, opera: 106, ie: false }
66
76
  }
@@ -30,7 +30,7 @@ module ActionController # :nodoc:
30
30
  # parameter.
31
31
  #
32
32
  # If you want to use multiple rate limits per controller, you need to give each of
33
- # them and explicit name via the `name:` option.
33
+ # them an explicit name via the `name:` option.
34
34
  #
35
35
  # Examples:
36
36
  #
@@ -2,8 +2,6 @@
2
2
 
3
3
  # :markup: markdown
4
4
 
5
- require "set"
6
-
7
5
  module ActionController
8
6
  # See Renderers.add
9
7
  def self.add_renderer(key, &block)
@@ -10,7 +10,6 @@ require "active_support/deep_mergeable"
10
10
  require "action_dispatch/http/upload"
11
11
  require "rack/test"
12
12
  require "stringio"
13
- require "set"
14
13
  require "yaml"
15
14
 
16
15
  module ActionController
@@ -262,20 +261,6 @@ module ActionController
262
261
  cattr_accessor :always_permitted_parameters, default: %w( controller action )
263
262
 
264
263
  class << self
265
- def allow_deprecated_parameters_hash_equality
266
- ActionController.deprecator.warn <<-WARNING.squish
267
- `Rails.application.config.action_controller.allow_deprecated_parameters_hash_equality` is
268
- deprecated and will be removed in Rails 8.0.
269
- WARNING
270
- end
271
-
272
- def allow_deprecated_parameters_hash_equality=(value)
273
- ActionController.deprecator.warn <<-WARNING.squish
274
- `Rails.application.config.action_controller.allow_deprecated_parameters_hash_equality`
275
- is deprecated and will be removed in Rails 8.0.
276
- WARNING
277
- end
278
-
279
264
  def nested_attribute?(key, value) # :nodoc:
280
265
  /\A-?\d+\z/.match?(key) && (value.is_a?(Hash) || value.is_a?(Parameters))
281
266
  end
@@ -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|
@@ -108,7 +108,7 @@ module ActionController
108
108
  set_header k, "application/x-www-form-urlencoded"
109
109
  end
110
110
 
111
- case content_mime_type.to_sym
111
+ case content_mime_type&.to_sym
112
112
  when nil
113
113
  raise "Unknown Content-Type: #{content_type}"
114
114
  when :json
@@ -123,7 +123,7 @@ module ActionController
123
123
  end
124
124
  end
125
125
 
126
- data_stream = StringIO.new(data)
126
+ data_stream = StringIO.new(data.b)
127
127
  set_header "CONTENT_LENGTH", data_stream.length.to_s
128
128
  set_header "rack.input", data_stream
129
129
  end
@@ -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
  #
@@ -227,8 +226,7 @@ module ActionDispatch # :nodoc:
227
226
  end
228
227
  end
229
228
 
230
- # Enable the [report-uri]
231
- # (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)
232
230
  # directive. Violation reports will be sent to the
233
231
  # specified URI:
234
232
  #
@@ -238,8 +236,7 @@ module ActionDispatch # :nodoc:
238
236
  @directives["report-uri"] = [uri]
239
237
  end
240
238
 
241
- # Specify asset types for which [Subresource Integrity]
242
- # (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:
243
240
  #
244
241
  # policy.require_sri_for :script, :style
245
242
  #
@@ -255,8 +252,7 @@ module ActionDispatch # :nodoc:
255
252
  end
256
253
  end
257
254
 
258
- # Specify whether a [sandbox]
259
- # (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)
260
256
  # should be enabled for the requested resource:
261
257
  #
262
258
  # policy.sandbox
@@ -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
@@ -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
@@ -63,6 +63,9 @@ module ActionDispatch
63
63
 
64
64
  def initialize(env)
65
65
  super
66
+
67
+ @rack_request = Rack::Request.new(env)
68
+
66
69
  @method = nil
67
70
  @request_method = nil
68
71
  @remote_ip = nil
@@ -71,6 +74,8 @@ module ActionDispatch
71
74
  @ip = nil
72
75
  end
73
76
 
77
+ attr_reader :rack_request
78
+
74
79
  def commit_cookie_jar! # :nodoc:
75
80
  end
76
81
 
@@ -388,15 +393,12 @@ module ActionDispatch
388
393
  # Override Rack's GET method to support indifferent access.
389
394
  def GET
390
395
  fetch_header("action_dispatch.request.query_parameters") do |k|
391
- rack_query_params = super || {}
392
- controller = path_parameters[:controller]
393
- action = path_parameters[:action]
394
- rack_query_params = Request::Utils.set_binary_encoding(self, rack_query_params, controller, action)
395
- # Check for non UTF-8 parameter values, which would cause errors later
396
- Request::Utils.check_param_encoding(rack_query_params)
397
- 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
398
400
  end
399
- rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError, Rack::QueryParser::ParamsTooDeepError => e
401
+ rescue ActionDispatch::ParamError => e
400
402
  raise ActionController::BadRequest.new("Invalid query parameters: #{e.message}")
401
403
  end
402
404
  alias :query_parameters :GET
@@ -404,18 +406,54 @@ module ActionDispatch
404
406
  # Override Rack's POST method to support indifferent access.
405
407
  def POST
406
408
  fetch_header("action_dispatch.request.request_parameters") do
407
- pr = parse_formatted_parameters(params_parsers) do |params|
408
- 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
420
+ end
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)
409
427
  end
410
- pr = Request::Utils.set_binary_encoding(self, pr, path_parameters[:controller], path_parameters[:action])
411
- Request::Utils.check_param_encoding(pr)
412
- self.request_parameters = Request::Utils.normalize_encode_params(pr)
428
+
429
+ self.request_parameters = pr
413
430
  end
414
- rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError, Rack::QueryParser::ParamsTooDeepError, EOFError => e
431
+ rescue ActionDispatch::ParamError, EOFError => e
415
432
  raise ActionController::BadRequest.new("Invalid request parameters: #{e.message}")
416
433
  end
417
434
  alias :request_parameters :POST
418
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
+
419
457
  # Returns the authorization header regardless of whether it was specified
420
458
  # directly or through one of the proxy alternatives.
421
459
  def authorization
@@ -492,6 +530,10 @@ module ActionDispatch
492
530
  yield
493
531
  end
494
532
  end
533
+
534
+ def fallback_request_parameters
535
+ rack_request.POST
536
+ end
495
537
  end
496
538
  end
497
539
 
@@ -15,17 +15,12 @@ module ActionDispatch
15
15
  paths = RESCUES_TEMPLATE_PATHS.dup
16
16
  lookup_context = ActionView::LookupContext.new(paths)
17
17
  super(lookup_context, assigns, nil)
18
- @exception_wrapper = assigns[:exception_wrapper]
19
18
  end
20
19
 
21
20
  def compiled_method_container
22
21
  self.class
23
22
  end
24
23
 
25
- def error_highlight_available?
26
- @exception_wrapper.error_highlight_available?
27
- end
28
-
29
24
  def debug_params(params)
30
25
  clean_params = params.clone
31
26
  clean_params.delete("action")
@@ -201,12 +201,6 @@ module ActionDispatch
201
201
  end
202
202
  end
203
203
 
204
- def error_highlight_available?
205
- # ErrorHighlight.spot with backtrace_location keyword is available since
206
- # error_highlight 0.4.0
207
- defined?(ErrorHighlight) && Gem::Version.new(ErrorHighlight::VERSION) >= Gem::Version.new("0.4.0")
208
- end
209
-
210
204
  def trace_to_show
211
205
  if traces["Application Trace"].empty? && rescue_template != "routing_error"
212
206
  "Full Trace"
@@ -28,9 +28,6 @@
28
28
  </tr>
29
29
  </table>
30
30
  </div>
31
- <%- unless self.error_highlight_available? -%>
32
- <p class="error_highlight_tip">Tip: You may want to add <code>gem "error_highlight", "&gt;= 0.4.0"</code> into your Gemfile, which will display the fine-grained error location.</p>
33
- <%- end -%>
34
31
  </div>
35
32
  <% end %>
36
33
  <% end %>
@@ -83,8 +83,8 @@ module ActionDispatch
83
83
  end
84
84
 
85
85
  class CustomParamEncoder # :nodoc:
86
- def self.encode(request, params, controller, action)
87
- return params unless controller && controller.valid_encoding? && encoding_template = action_encoding_template(request, controller, action)
86
+ def self.encode_for_template(params, encoding_template)
87
+ return params unless encoding_template
88
88
  params.except(:controller, :action).each do |key, value|
89
89
  ActionDispatch::Request::Utils.each_param_value(value) do |param|
90
90
  # If `param` is frozen, it comes from the router defaults
@@ -98,8 +98,14 @@ module ActionDispatch
98
98
  params
99
99
  end
100
100
 
101
+ def self.encode(request, params, controller, action)
102
+ encoding_template = action_encoding_template(request, controller, action)
103
+ encode_for_template(params, encoding_template)
104
+ end
105
+
101
106
  def self.action_encoding_template(request, controller, action) # :nodoc:
102
- request.controller_class_for(controller).action_encoding_template(action)
107
+ controller && controller.valid_encoding? &&
108
+ request.controller_class_for(controller).action_encoding_template(action)
103
109
  rescue MissingController
104
110
  nil
105
111
  end
@@ -375,35 +375,18 @@ module ActionDispatch
375
375
  Routing::RouteSet::Dispatcher.new raise_on_name_error
376
376
  end
377
377
 
378
- if Thread.respond_to?(:each_caller_location)
379
- def route_source_location
380
- if Mapper.route_source_locations
381
- action_dispatch_dir = File.expand_path("..", __dir__)
382
- Thread.each_caller_location do |location|
383
- next if location.path.start_with?(action_dispatch_dir)
378
+ def route_source_location
379
+ if Mapper.route_source_locations
380
+ action_dispatch_dir = File.expand_path("..", __dir__)
381
+ Thread.each_caller_location do |location|
382
+ next if location.path.start_with?(action_dispatch_dir)
384
383
 
385
- cleaned_path = Mapper.backtrace_cleaner.clean_frame(location.path)
386
- next if cleaned_path.nil?
384
+ cleaned_path = Mapper.backtrace_cleaner.clean_frame(location.path)
385
+ next if cleaned_path.nil?
387
386
 
388
- return "#{cleaned_path}:#{location.lineno}"
389
- end
390
- nil
391
- end
392
- end
393
- else
394
- def route_source_location
395
- if Mapper.route_source_locations
396
- action_dispatch_dir = File.expand_path("..", __dir__)
397
- caller_locations.each do |location|
398
- next if location.path.start_with?(action_dispatch_dir)
399
-
400
- cleaned_path = Mapper.backtrace_cleaner.clean_frame(location.path)
401
- next if cleaned_path.nil?
402
-
403
- return "#{cleaned_path}:#{location.lineno}"
404
- end
405
- nil
387
+ return "#{cleaned_path}:#{location.lineno}"
406
388
  end
389
+ nil
407
390
  end
408
391
  end
409
392
  end
@@ -1179,6 +1162,16 @@ module ActionDispatch
1179
1162
  CANONICAL_ACTIONS = %w(index create new show update destroy)
1180
1163
 
1181
1164
  class Resource # :nodoc:
1165
+ class << self
1166
+ def default_actions(api_only)
1167
+ if api_only
1168
+ [:index, :create, :show, :update, :destroy]
1169
+ else
1170
+ [:index, :create, :new, :show, :update, :destroy, :edit]
1171
+ end
1172
+ end
1173
+ end
1174
+
1182
1175
  attr_reader :controller, :path, :param
1183
1176
 
1184
1177
  def initialize(entities, api_only, shallow, options = {})
@@ -1186,6 +1179,11 @@ module ActionDispatch
1186
1179
  raise ArgumentError, ":param option can't contain colons"
1187
1180
  end
1188
1181
 
1182
+ valid_actions = self.class.default_actions(false) # ignore api_only for this validation
1183
+ if invalid_actions = invalid_only_except_options(options, valid_actions).presence
1184
+ raise ArgumentError, ":only and :except must include only #{valid_actions}, but also included #{invalid_actions}"
1185
+ end
1186
+
1189
1187
  @name = entities.to_s
1190
1188
  @path = (options[:path] || @name).to_s
1191
1189
  @controller = (options[:controller] || @name).to_s
@@ -1199,11 +1197,7 @@ module ActionDispatch
1199
1197
  end
1200
1198
 
1201
1199
  def default_actions
1202
- if @api_only
1203
- [:index, :create, :show, :update, :destroy]
1204
- else
1205
- [:index, :create, :new, :show, :update, :destroy, :edit]
1206
- end
1200
+ self.class.default_actions(@api_only)
1207
1201
  end
1208
1202
 
1209
1203
  def actions
@@ -1271,9 +1265,24 @@ module ActionDispatch
1271
1265
  end
1272
1266
 
1273
1267
  def singleton?; false; end
1268
+
1269
+ private
1270
+ def invalid_only_except_options(options, valid_actions)
1271
+ options.values_at(:only, :except).flatten.compact.uniq.map(&:to_sym) - valid_actions
1272
+ end
1274
1273
  end
1275
1274
 
1276
1275
  class SingletonResource < Resource # :nodoc:
1276
+ class << self
1277
+ def default_actions(api_only)
1278
+ if api_only
1279
+ [:show, :create, :update, :destroy]
1280
+ else
1281
+ [:show, :create, :update, :destroy, :new, :edit]
1282
+ end
1283
+ end
1284
+ end
1285
+
1277
1286
  def initialize(entities, api_only, shallow, options)
1278
1287
  super
1279
1288
  @as = nil
@@ -1282,11 +1291,7 @@ module ActionDispatch
1282
1291
  end
1283
1292
 
1284
1293
  def default_actions
1285
- if @api_only
1286
- [:show, :create, :update, :destroy]
1287
- else
1288
- [:show, :create, :update, :destroy, :new, :edit]
1289
- end
1294
+ self.class.default_actions(@api_only)
1290
1295
  end
1291
1296
 
1292
1297
  def plural
@@ -1347,7 +1352,7 @@ module ActionDispatch
1347
1352
  end
1348
1353
 
1349
1354
  with_scope_level(:resource) do
1350
- options = apply_action_options options
1355
+ options = apply_action_options :resource, options
1351
1356
  resource_scope(SingletonResource.new(resources.pop, api_only?, @scope[:shallow], options)) do
1352
1357
  yield if block_given?
1353
1358
 
@@ -1517,7 +1522,7 @@ module ActionDispatch
1517
1522
  end
1518
1523
 
1519
1524
  with_scope_level(:resources) do
1520
- options = apply_action_options options
1525
+ options = apply_action_options :resources, options
1521
1526
  resource_scope(Resource.new(resources.pop, api_only?, @scope[:shallow], options)) do
1522
1527
  yield if block_given?
1523
1528
 
@@ -1786,17 +1791,32 @@ module ActionDispatch
1786
1791
  false
1787
1792
  end
1788
1793
 
1789
- def apply_action_options(options)
1794
+ def apply_action_options(method, options)
1790
1795
  return options if action_options? options
1791
- options.merge scope_action_options
1796
+ options.merge scope_action_options(method)
1792
1797
  end
1793
1798
 
1794
1799
  def action_options?(options)
1795
1800
  options[:only] || options[:except]
1796
1801
  end
1797
1802
 
1798
- def scope_action_options
1799
- @scope[:action_options] || {}
1803
+ def scope_action_options(method)
1804
+ return {} unless @scope[:action_options]
1805
+
1806
+ actions = applicable_actions_for(method)
1807
+ @scope[:action_options].dup.tap do |options|
1808
+ (options[:only] = Array(options[:only]) & actions) if options[:only]
1809
+ (options[:except] = Array(options[:except]) & actions) if options[:except]
1810
+ end
1811
+ end
1812
+
1813
+ def applicable_actions_for(method)
1814
+ case method
1815
+ when :resource
1816
+ SingletonResource.default_actions(api_only?)
1817
+ when :resources
1818
+ Resource.default_actions(api_only?)
1819
+ end
1800
1820
  end
1801
1821
 
1802
1822
  def resource_scope?
@@ -2209,8 +2229,8 @@ module ActionDispatch
2209
2229
  end
2210
2230
 
2211
2231
  # Define custom polymorphic mappings of models to URLs. This alters the behavior
2212
- # of `polymorphic_url` and consequently the behavior of `link_to` and `form_for`
2213
- # when passed a model instance, e.g:
2232
+ # of `polymorphic_url` and consequently the behavior of `link_to`, `form_with`
2233
+ # and `form_for` when passed a model instance, e.g:
2214
2234
  #
2215
2235
  # resource :basket
2216
2236
  #
@@ -2219,7 +2239,7 @@ module ActionDispatch
2219
2239
  # end
2220
2240
  #
2221
2241
  # This will now generate "/basket" when a `Basket` instance is passed to
2222
- # `link_to` or `form_for` instead of the standard "/baskets/:id".
2242
+ # `link_to`, `form_with` or `form_for` instead of the standard "/baskets/:id".
2223
2243
  #
2224
2244
  # NOTE: This custom behavior only applies to simple polymorphic URLs where a
2225
2245
  # single model instance is passed and not more complicated forms, e.g:
@@ -31,7 +31,7 @@ module ActionDispatch
31
31
  # * `url_for`, so you can use it with a record as the argument, e.g.
32
32
  # `url_for(@article)`;
33
33
  # * ActionView::Helpers::FormHelper uses `polymorphic_path`, so you can write
34
- # `form_for(@article)` without having to specify `:url` parameter for the
34
+ # `form_with(model: @article)` without having to specify `:url` parameter for the
35
35
  # form action;
36
36
  # * `redirect_to` (which, in fact, uses `url_for`) so you can write
37
37
  # `redirect_to(post)` in your controllers;
@@ -61,7 +61,7 @@ module ActionDispatch
61
61
  # argument to the method. For example:
62
62
  #
63
63
  # polymorphic_url([blog, @post]) # calls blog.post_path(@post)
64
- # form_for([blog, @post]) # => "/blog/posts/1"
64
+ # form_with(model: [blog, @post]) # => "/blog/posts/1"
65
65
  #
66
66
  module PolymorphicRoutes
67
67
  # Constructs a call to a named RESTful route for the given record and returns
@@ -659,14 +659,14 @@ module ActionDispatch
659
659
  if route.segment_keys.include?(:controller)
660
660
  ActionDispatch.deprecator.warn(<<-MSG.squish)
661
661
  Using a dynamic :controller segment in a route is deprecated and
662
- will be removed in Rails 7.2.
662
+ will be removed in Rails 8.1.
663
663
  MSG
664
664
  end
665
665
 
666
666
  if route.segment_keys.include?(:action)
667
667
  ActionDispatch.deprecator.warn(<<-MSG.squish)
668
668
  Using a dynamic :action segment in a route is deprecated and
669
- will be removed in Rails 7.2.
669
+ will be removed in Rails 8.1.
670
670
  MSG
671
671
  end
672
672
 
@@ -87,8 +87,13 @@ module ActionDispatch
87
87
  end
88
88
 
89
89
  def generate_response_message(expected, actual = @response.response_code)
90
- (+"Expected response to be a <#{code_with_name(expected)}>,"\
91
- " but was a <#{code_with_name(actual)}>").concat(location_if_redirected).concat(response_body_if_short)
90
+ lambda do
91
+ (+"Expected response to be a <#{code_with_name(expected)}>,"\
92
+ " but was a <#{code_with_name(actual)}>").
93
+ concat(location_if_redirected).
94
+ concat(exception_if_present).
95
+ concat(response_body_if_short)
96
+ end
92
97
  end
93
98
 
94
99
  def response_body_if_short
@@ -96,6 +101,11 @@ module ActionDispatch
96
101
  "\nResponse body: #{@response.body}"
97
102
  end
98
103
 
104
+ def exception_if_present
105
+ return "" unless ex = @request&.env&.[]("action_dispatch.exception")
106
+ "\n\nException while processing request: #{Minitest::UnexpectedError.new(ex).message}\n"
107
+ end
108
+
99
109
  def location_if_redirected
100
110
  return "" unless @response.redirection? && @response.location.present?
101
111
  location = normalize_argument_to_redirection(@response.location)
@@ -118,9 +118,9 @@ module ActionDispatch
118
118
  # assert_equal "/users", users_path
119
119
  # end
120
120
  #
121
- def with_routing(&block)
121
+ def with_routing(config = nil, &block)
122
122
  old_routes, old_controller = @routes, @controller
123
- create_routes(&block)
123
+ create_routes(config, &block)
124
124
  ensure
125
125
  reset_routes(old_routes, old_controller)
126
126
  end
@@ -267,8 +267,8 @@ module ActionDispatch
267
267
  end
268
268
 
269
269
  private
270
- def create_routes
271
- @routes = ActionDispatch::Routing::RouteSet.new
270
+ def create_routes(config = nil)
271
+ @routes = ActionDispatch::Routing::RouteSet.new(config || ActionDispatch::Routing::RouteSet::DEFAULT_CONFIG)
272
272
  if @controller
273
273
  @controller = @controller.clone
274
274
  _routes = @routes
@@ -284,7 +284,17 @@ module ActionDispatch
284
284
 
285
285
  # NOTE: rack-test v0.5 doesn't build a default uri correctly Make sure requested
286
286
  # path is always a full URI.
287
- session.request(build_full_uri(path, request_env), request_env)
287
+ uri = build_full_uri(path, request_env)
288
+
289
+ if method == :get && String === request_env[:params]
290
+ # rack-test will needlessly parse and rebuild a :params
291
+ # querystring, using Rack's query parser. At best that's a
292
+ # waste of time; at worst it can change the value.
293
+
294
+ uri << "?" << request_env.delete(:params)
295
+ end
296
+
297
+ session.request(uri, request_env)
288
298
 
289
299
  @request_count += 1
290
300
  @request = ActionDispatch::Request.new(session.last_request.env)
@@ -53,7 +53,13 @@ module ActionDispatch
53
53
  eager_autoload do
54
54
  autoload_under "http" do
55
55
  autoload :ContentSecurityPolicy
56
+ autoload :InvalidParameterError, "action_dispatch/http/param_error"
57
+ autoload :ParamBuilder
58
+ autoload :ParamError
59
+ autoload :ParameterTypeError, "action_dispatch/http/param_error"
60
+ autoload :ParamsTooDeepError, "action_dispatch/http/param_error"
56
61
  autoload :PermissionsPolicy
62
+ autoload :QueryParser
57
63
  autoload :Request
58
64
  autoload :Response
59
65
  end
@@ -12,7 +12,7 @@ module ActionPack
12
12
  MAJOR = 8
13
13
  MINOR = 0
14
14
  TINY = 0
15
- PRE = "beta1"
15
+ PRE = "rc1"
16
16
 
17
17
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
18
18
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: actionpack
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.0.0.beta1
4
+ version: 8.0.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-09-26 00:00:00.000000000 Z
11
+ date: 2024-10-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 8.0.0.beta1
19
+ version: 8.0.0.rc1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 8.0.0.beta1
26
+ version: 8.0.0.rc1
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: nokogiri
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -128,28 +128,28 @@ dependencies:
128
128
  requirements:
129
129
  - - '='
130
130
  - !ruby/object:Gem::Version
131
- version: 8.0.0.beta1
131
+ version: 8.0.0.rc1
132
132
  type: :runtime
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
136
  - - '='
137
137
  - !ruby/object:Gem::Version
138
- version: 8.0.0.beta1
138
+ version: 8.0.0.rc1
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: activemodel
141
141
  requirement: !ruby/object:Gem::Requirement
142
142
  requirements:
143
143
  - - '='
144
144
  - !ruby/object:Gem::Version
145
- version: 8.0.0.beta1
145
+ version: 8.0.0.rc1
146
146
  type: :development
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
150
  - - '='
151
151
  - !ruby/object:Gem::Version
152
- version: 8.0.0.beta1
152
+ version: 8.0.0.rc1
153
153
  description: Web apps on Rails. Simple, battle-tested conventions for building and
154
154
  testing MVC web applications. Works with any Rack-compatible server.
155
155
  email: david@loudthinking.com
@@ -233,8 +233,11 @@ files:
233
233
  - lib/action_dispatch/http/mime_negotiation.rb
234
234
  - lib/action_dispatch/http/mime_type.rb
235
235
  - lib/action_dispatch/http/mime_types.rb
236
+ - lib/action_dispatch/http/param_builder.rb
237
+ - lib/action_dispatch/http/param_error.rb
236
238
  - lib/action_dispatch/http/parameters.rb
237
239
  - lib/action_dispatch/http/permissions_policy.rb
240
+ - lib/action_dispatch/http/query_parser.rb
238
241
  - lib/action_dispatch/http/rack_cache.rb
239
242
  - lib/action_dispatch/http/request.rb
240
243
  - lib/action_dispatch/http/response.rb
@@ -347,10 +350,10 @@ licenses:
347
350
  - MIT
348
351
  metadata:
349
352
  bug_tracker_uri: https://github.com/rails/rails/issues
350
- changelog_uri: https://github.com/rails/rails/blob/v8.0.0.beta1/actionpack/CHANGELOG.md
351
- documentation_uri: https://api.rubyonrails.org/v8.0.0.beta1/
353
+ changelog_uri: https://github.com/rails/rails/blob/v8.0.0.rc1/actionpack/CHANGELOG.md
354
+ documentation_uri: https://api.rubyonrails.org/v8.0.0.rc1/
352
355
  mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
353
- source_code_uri: https://github.com/rails/rails/tree/v8.0.0.beta1/actionpack
356
+ source_code_uri: https://github.com/rails/rails/tree/v8.0.0.rc1/actionpack
354
357
  rubygems_mfa_required: 'true'
355
358
  post_install_message:
356
359
  rdoc_options: []