actionpack 8.0.0.beta1 → 8.0.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +48 -0
  3. data/lib/abstract_controller/helpers.rb +2 -0
  4. data/lib/abstract_controller/rendering.rb +0 -1
  5. data/lib/action_controller/form_builder.rb +3 -3
  6. data/lib/action_controller/metal/allow_browser.rb +12 -2
  7. data/lib/action_controller/metal/http_authentication.rb +1 -4
  8. data/lib/action_controller/metal/rate_limiting.rb +1 -1
  9. data/lib/action_controller/metal/renderers.rb +0 -2
  10. data/lib/action_controller/metal/strong_parameters.rb +3 -16
  11. data/lib/action_controller/railtie.rb +0 -6
  12. data/lib/action_controller/test_case.rb +2 -2
  13. data/lib/action_dispatch/http/content_security_policy.rb +4 -8
  14. data/lib/action_dispatch/http/filter_parameters.rb +9 -4
  15. data/lib/action_dispatch/http/filter_redirect.rb +9 -2
  16. data/lib/action_dispatch/http/param_builder.rb +186 -0
  17. data/lib/action_dispatch/http/param_error.rb +26 -0
  18. data/lib/action_dispatch/http/query_parser.rb +53 -0
  19. data/lib/action_dispatch/http/request.rb +56 -14
  20. data/lib/action_dispatch/journey/scanner.rb +5 -1
  21. data/lib/action_dispatch/middleware/debug_view.rb +0 -5
  22. data/lib/action_dispatch/middleware/exception_wrapper.rb +0 -6
  23. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +0 -3
  24. data/lib/action_dispatch/railtie.rb +6 -0
  25. data/lib/action_dispatch/request/utils.rb +9 -3
  26. data/lib/action_dispatch/routing/mapper.rb +65 -45
  27. data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -2
  28. data/lib/action_dispatch/routing/route_set.rb +2 -2
  29. data/lib/action_dispatch/testing/assertions/response.rb +12 -2
  30. data/lib/action_dispatch/testing/assertions/routing.rb +4 -4
  31. data/lib/action_dispatch/testing/integration.rb +11 -1
  32. data/lib/action_dispatch.rb +6 -0
  33. data/lib/action_pack/gem_version.rb +1 -1
  34. metadata +14 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4392b60cf6c4af7ed943daf2c4e438440689b8f869a859510b1e0e5808204cf2
4
- data.tar.gz: ee9993b25b397af527f1730dc439a0425a81d4b03afb148e310dac8862003261
3
+ metadata.gz: 76724412ee5fbe34b92080713c9e9fab617fe1a81c4400ba2ee252d55020a6e3
4
+ data.tar.gz: 14b34e7e8e188f66b7f7da1542301fa5856fb0b14cb6213282c07c30fffbd76f
5
5
  SHA512:
6
- metadata.gz: 2063ad6ca3243b066226af6a1d1ad616d808c48a18f80df374d02b064ad4839a971e4eff804321621d910111829b0c1130b5a05f5a44afabb380ec9f7d5985b7
7
- data.tar.gz: f3f1e1ce84c47654f69cf3f3ada2afa48e412dac1b7a5abd10c4d0fc34b66b7574f2454410c323cfa26a000005da585815db8a27dc3f1e9c2a71a504b0f3021d
6
+ metadata.gz: d13e4c2bc63c93db23db2ab94786700542926ba9c200f611985dd524fe7cd11602bc592a9124c14d7f08e39bf7e95ed56fbebdadb6c8391d5712bbad47fb62bf
7
+ data.tar.gz: 8f90ce2cd483f2ac438680310306293ccf733b0052f064bfb265a74a65c81e8bab5a32d699914a6f5c713974367d3ae5756a11e0e88210f4198e0d195339ce9b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,51 @@
1
+ ## Rails 8.0.0.rc2 (October 30, 2024) ##
2
+
3
+ * Fix routes with `::` in the path.
4
+
5
+ *Rafael Mendonça França*
6
+
7
+ * Maintain Rack 2 parameter parsing behaviour.
8
+
9
+ *Matthew Draper*
10
+
11
+
12
+ ## Rails 8.0.0.rc1 (October 19, 2024) ##
13
+
14
+ * Remove `Rails.application.config.action_controller.allow_deprecated_parameters_hash_equality`.
15
+
16
+ *Rafael Mendonça França*
17
+
18
+ * Improve `ActionController::TestCase` to expose a binary encoded `request.body`.
19
+
20
+ The rack spec clearly states:
21
+
22
+ > The input stream is an IO-like object which contains the raw HTTP POST data.
23
+ > When applicable, its external encoding must be “ASCII-8BIT” and it must be opened in binary mode.
24
+
25
+ Until now its encoding was generally UTF-8, which doesn't accurately reflect production
26
+ behavior.
27
+
28
+ *Jean Boussier*
29
+
30
+ * Update `ActionController::AllowBrowser` to support passing method names to `:block`
31
+
32
+ ```ruby
33
+ class ApplicationController < ActionController::Base
34
+ allow_browser versions: :modern, block: :handle_outdated_browser
35
+
36
+ private
37
+ def handle_outdated_browser
38
+ render file: Rails.root.join("public/custom-error.html"), status: :not_acceptable
39
+ end
40
+ end
41
+ ```
42
+
43
+ *Sean Doyle*
44
+
45
+ * Raise an `ArgumentError` when invalid `:only` or `:except` options are passed into `#resource` and `#resources`.
46
+
47
+ *Joshua Young*
48
+
1
49
  ## Rails 8.0.0.beta1 (September 26, 2024) ##
2
50
 
3
51
  * 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
  }
@@ -513,14 +513,11 @@ module ActionController
513
513
  array_params.each { |param| (param[1] || +"").gsub! %r/^"|"$/, "" }
514
514
  end
515
515
 
516
- WHITESPACED_AUTHN_PAIR_DELIMITERS = /\s*#{AUTHN_PAIR_DELIMITERS}\s*/
517
- private_constant :WHITESPACED_AUTHN_PAIR_DELIMITERS
518
-
519
516
  # This method takes an authorization body and splits up the key-value pairs by
520
517
  # the standardized `:`, `;`, or `\t` delimiters defined in
521
518
  # `AUTHN_PAIR_DELIMITERS`.
522
519
  def raw_params(auth)
523
- _raw_params = auth.sub(TOKEN_REGEX, "").split(WHITESPACED_AUTHN_PAIR_DELIMITERS)
520
+ _raw_params = auth.sub(TOKEN_REGEX, "").split(AUTHN_PAIR_DELIMITERS).map(&:strip)
524
521
  _raw_params.reject!(&:empty?)
525
522
 
526
523
  if !_raw_params.first&.start_with?(TOKEN_KEY)
@@ -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
@@ -96,6 +95,8 @@ module ActionController
96
95
  # * `permit` to filter params for mass assignment.
97
96
  # * `require` to require a parameter or raise an error.
98
97
  #
98
+ # Examples:
99
+ #
99
100
  # params = ActionController::Parameters.new({
100
101
  # person: {
101
102
  # name: "Francesco",
@@ -110,7 +111,7 @@ module ActionController
110
111
  # Person.first.update!(permitted)
111
112
  # # => #<Person id: 1, name: "Francesco", age: 22, role: "user">
112
113
  #
113
- # Paramaters provides two options that control the top-level behavior of new
114
+ # Parameters provides two options that control the top-level behavior of new
114
115
  # instances:
115
116
  #
116
117
  # * `permit_all_parameters` - If it's `true`, all the parameters will be
@@ -262,20 +263,6 @@ module ActionController
262
263
  cattr_accessor :always_permitted_parameters, default: %w( controller action )
263
264
 
264
265
  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
266
  def nested_attribute?(key, value) # :nodoc:
280
267
  /\A-?\d+\z/.match?(key) && (value.is_a?(Hash) || value.is_a?(Parameters))
281
268
  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
@@ -68,12 +68,17 @@ module ActionDispatch
68
68
  ActiveSupport::ParameterFilter.new(filters)
69
69
  end
70
70
 
71
- KV_RE = "[^&;=]+"
72
- PAIR_RE = %r{(#{KV_RE})=(#{KV_RE})}
73
71
  def filtered_query_string # :doc:
74
- query_string.gsub(PAIR_RE) do |_|
75
- parameter_filter.filter($1 => $2).first.join("=")
72
+ parts = query_string.split(/([&;])/)
73
+ filtered_parts = parts.map do |part|
74
+ if part.include?("=")
75
+ key, value = part.split("=", 2)
76
+ parameter_filter.filter(key => value).first.join("=")
77
+ else
78
+ part
79
+ end
76
80
  end
81
+ filtered_parts.join("")
77
82
  end
78
83
  end
79
84
  end
@@ -37,9 +37,16 @@ module ActionDispatch
37
37
  def parameter_filtered_location
38
38
  uri = URI.parse(location)
39
39
  unless uri.query.nil? || uri.query.empty?
40
- uri.query.gsub!(FilterParameters::PAIR_RE) do
41
- request.parameter_filter.filter($1 => $2).first.join("=")
40
+ parts = uri.query.split(/([&;])/)
41
+ filtered_parts = parts.map do |part|
42
+ if part.include?("=")
43
+ key, value = part.split("=", 2)
44
+ request.parameter_filter.filter(key => value).first.join("=")
45
+ else
46
+ part
47
+ end
42
48
  end
49
+ uri.query = filtered_parts.join("")
43
50
  end
44
51
  uri.to_s
45
52
  rescue URI::Error
@@ -0,0 +1,186 @@
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 :ignore_leading_brackets
20
+
21
+ LEADING_BRACKETS_COMPAT = defined?(::Rack::RELEASE) && ::Rack::RELEASE.to_s.start_with?("2.")
22
+
23
+ cattr_accessor :default
24
+ self.default = make_default(100)
25
+
26
+ class << self
27
+ delegate :from_query_string, :from_pairs, :from_hash, to: :default
28
+ end
29
+
30
+ def from_query_string(qs, separator: nil, encoding_template: nil)
31
+ from_pairs QueryParser.each_pair(qs, separator), encoding_template: encoding_template
32
+ end
33
+
34
+ def from_pairs(pairs, encoding_template: nil)
35
+ params = make_params
36
+
37
+ pairs.each do |k, v|
38
+ if Hash === v
39
+ v = ActionDispatch::Http::UploadedFile.new(v)
40
+ end
41
+
42
+ store_nested_param(params, k, v, 0, encoding_template)
43
+ end
44
+
45
+ params
46
+ rescue ArgumentError => e
47
+ raise InvalidParameterError, e.message, e.backtrace
48
+ end
49
+
50
+ def from_hash(hash, encoding_template: nil)
51
+ # Force encodings from encoding template
52
+ hash = Request::Utils::CustomParamEncoder.encode_for_template(hash, encoding_template)
53
+
54
+ # Assert valid encoding
55
+ Request::Utils.check_param_encoding(hash)
56
+
57
+ # Convert hashes to HWIA (or UploadedFile), and deep-munge nils
58
+ # out of arrays
59
+ hash = Request::Utils.normalize_encode_params(hash)
60
+
61
+ hash
62
+ end
63
+
64
+ private
65
+ def store_nested_param(params, name, v, depth, encoding_template = nil)
66
+ raise ParamsTooDeepError if depth >= param_depth_limit
67
+
68
+ if !name
69
+ # nil name, treat same as empty string (required by tests)
70
+ k = after = ""
71
+ elsif depth == 0
72
+ if ignore_leading_brackets || (ignore_leading_brackets.nil? && LEADING_BRACKETS_COMPAT)
73
+ # Rack 2 compatible behavior, ignore leading brackets
74
+ if name =~ /\A[\[\]]*([^\[\]]+)\]*/
75
+ k = $1
76
+ after = $' || ""
77
+
78
+ if !ignore_leading_brackets && (k != $& || !after.empty? && !after.start_with?("["))
79
+ ActionDispatch.deprecator.warn("Skipping over leading brackets in parameter name #{name.inspect} is deprecated and will parse differently in Rails 8.1 or Rack 3.0.")
80
+ end
81
+ else
82
+ k = name
83
+ after = ""
84
+ end
85
+ else
86
+ # Start of parsing, don't treat [] or [ at start of string specially
87
+ if start = name.index("[", 1)
88
+ # Start of parameter nesting, use part before brackets as key
89
+ k = name[0, start]
90
+ after = name[start, name.length]
91
+ else
92
+ # Plain parameter with no nesting
93
+ k = name
94
+ after = ""
95
+ end
96
+ end
97
+ elsif name.start_with?("[]")
98
+ # Array nesting
99
+ k = "[]"
100
+ after = name[2, name.length]
101
+ elsif name.start_with?("[") && (start = name.index("]", 1))
102
+ # Hash nesting, use the part inside brackets as the key
103
+ k = name[1, start - 1]
104
+ after = name[start + 1, name.length]
105
+ else
106
+ # Probably malformed input, nested but not starting with [
107
+ # treat full name as key for backwards compatibility.
108
+ k = name
109
+ after = ""
110
+ end
111
+
112
+ return if k.empty?
113
+
114
+ if depth == 0 && String === v
115
+ # We have to wait until we've found the top part of the name,
116
+ # because that's what the encoding template is configured with
117
+ if encoding_template && (designated_encoding = encoding_template[k]) && !v.frozen?
118
+ v.force_encoding(designated_encoding)
119
+ end
120
+
121
+ # ... and we can't validate the encoding until after we've
122
+ # applied any template override
123
+ unless v.valid_encoding?
124
+ raise InvalidParameterError, "Invalid encoding for parameter: #{v.scrub}"
125
+ end
126
+ end
127
+
128
+ if after == ""
129
+ if k == "[]" && depth != 0
130
+ return (v || !ActionDispatch::Request::Utils.perform_deep_munge) ? [v] : []
131
+ else
132
+ params[k] = v
133
+ end
134
+ elsif after == "["
135
+ params[name] = v
136
+ elsif after == "[]"
137
+ params[k] ||= []
138
+ raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
139
+ params[k] << v if v || !ActionDispatch::Request::Utils.perform_deep_munge
140
+ elsif after.start_with?("[]")
141
+ # Recognize x[][y] (hash inside array) parameters
142
+ unless after[2] == "[" && after.end_with?("]") && (child_key = after[3, after.length - 4]) && !child_key.empty? && !child_key.index("[") && !child_key.index("]")
143
+ # Handle other nested array parameters
144
+ child_key = after[2, after.length]
145
+ end
146
+ params[k] ||= []
147
+ raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
148
+ if params_hash_type?(params[k].last) && !params_hash_has_key?(params[k].last, child_key)
149
+ store_nested_param(params[k].last, child_key, v, depth + 1)
150
+ else
151
+ params[k] << store_nested_param(make_params, child_key, v, depth + 1)
152
+ end
153
+ else
154
+ params[k] ||= make_params
155
+ raise ParameterTypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
156
+ params[k] = store_nested_param(params[k], after, v, depth + 1)
157
+ end
158
+
159
+ params
160
+ end
161
+
162
+ def make_params
163
+ ActiveSupport::HashWithIndifferentAccess.new
164
+ end
165
+
166
+ def new_depth_limit(param_depth_limit)
167
+ self.class.new @params_class, param_depth_limit
168
+ end
169
+
170
+ def params_hash_type?(obj)
171
+ Hash === obj
172
+ end
173
+
174
+ def params_hash_has_key?(hash, key)
175
+ return false if key.include?("[]")
176
+
177
+ key.split(/[\[\]]+/).inject(hash) do |h, part|
178
+ next h if part == ""
179
+ return false unless params_hash_type?(h) && h.key?(part)
180
+ h[part]
181
+ end
182
+
183
+ true
184
+ end
185
+ end
186
+ 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,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+ require "rack"
5
+
6
+ module ActionDispatch
7
+ class QueryParser
8
+ DEFAULT_SEP = /& */n
9
+ COMPAT_SEP = /[&;] */n
10
+ COMMON_SEP = { ";" => /; */n, ";," => /[;,] */n, "&" => /& */n, "&;" => /[&;] */n }
11
+
12
+ cattr_accessor :strict_query_string_separator
13
+
14
+ SEMICOLON_COMPAT = defined?(::Rack::QueryParser::DEFAULT_SEP) && ::Rack::QueryParser::DEFAULT_SEP.to_s.include?(";")
15
+
16
+ #--
17
+ # Note this departs from WHATWG's specified parsing algorithm by
18
+ # giving a nil value for keys that do not use '='. Callers that need
19
+ # the standard's interpretation can use `v.to_s`.
20
+ def self.each_pair(s, separator = nil)
21
+ return enum_for(:each_pair, s, separator) unless block_given?
22
+
23
+ s ||= ""
24
+
25
+ splitter =
26
+ if separator
27
+ COMMON_SEP[separator] || /[#{separator}] */n
28
+ elsif strict_query_string_separator
29
+ DEFAULT_SEP
30
+ elsif SEMICOLON_COMPAT && s.include?(";")
31
+ if strict_query_string_separator.nil?
32
+ ActionDispatch.deprecator.warn("Using semicolon as a query string separator is deprecated and will not be supported in Rails 8.1 or Rack 3.0. Use `&` instead.")
33
+ end
34
+ COMPAT_SEP
35
+ else
36
+ DEFAULT_SEP
37
+ end
38
+
39
+ s.split(splitter).each do |part|
40
+ next if part.empty?
41
+
42
+ k, v = part.split("=", 2)
43
+
44
+ k = URI.decode_www_form_component(k)
45
+ v &&= URI.decode_www_form_component(v)
46
+
47
+ yield k, v
48
+ end
49
+
50
+ nil
51
+ end
52
+ end
53
+ 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
 
@@ -55,7 +55,7 @@ module ActionDispatch
55
55
  def scan
56
56
  next_byte = @scanner.peek_byte
57
57
  case
58
- when (token = STATIC_TOKENS[next_byte])
58
+ when (token = STATIC_TOKENS[next_byte]) && (token != :SYMBOL || next_byte_is_not_a_token?)
59
59
  @scanner.pos += 1
60
60
  @length = @scanner.skip(/\w+/).to_i + 1 if token == :SYMBOL || token == :STAR
61
61
  token
@@ -65,6 +65,10 @@ module ActionDispatch
65
65
  :LITERAL
66
66
  end
67
67
  end
68
+
69
+ def next_byte_is_not_a_token?
70
+ !STATIC_TOKENS[@scanner.string.getbyte(@scanner.pos + 1)]
71
+ end
68
72
  end
69
73
  end
70
74
  end
@@ -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 %>
@@ -31,6 +31,9 @@ module ActionDispatch
31
31
  config.action_dispatch.debug_exception_log_level = :fatal
32
32
  config.action_dispatch.strict_freshness = false
33
33
 
34
+ config.action_dispatch.ignore_leading_brackets = nil
35
+ config.action_dispatch.strict_query_string_separator = nil
36
+
34
37
  config.action_dispatch.default_headers = {
35
38
  "X-Frame-Options" => "SAMEORIGIN",
36
39
  "X-XSS-Protection" => "1; mode=block",
@@ -52,6 +55,9 @@ module ActionDispatch
52
55
  ActionDispatch::Http::URL.secure_protocol = app.config.force_ssl
53
56
  ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length
54
57
 
58
+ ActionDispatch::ParamBuilder.ignore_leading_brackets = app.config.action_dispatch.ignore_leading_brackets
59
+ ActionDispatch::QueryParser.strict_query_string_separator = app.config.action_dispatch.strict_query_string_separator
60
+
55
61
  ActiveSupport.on_load(:action_dispatch_request) do
56
62
  self.ignore_accept_header = app.config.action_dispatch.ignore_accept_header
57
63
  ActionDispatch::Request::Utils.perform_deep_munge = app.config.action_dispatch.perform_deep_munge
@@ -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 = "rc2"
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.rc2
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-30 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.rc2
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.rc2
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.rc2
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.rc2
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.rc2
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.rc2
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.rc2/actionpack/CHANGELOG.md
354
+ documentation_uri: https://api.rubyonrails.org/v8.0.0.rc2/
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.rc2/actionpack
354
357
  rubygems_mfa_required: 'true'
355
358
  post_install_message:
356
359
  rdoc_options: []