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.
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: []