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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +48 -0
- data/lib/abstract_controller/helpers.rb +2 -0
- data/lib/abstract_controller/rendering.rb +0 -1
- data/lib/action_controller/form_builder.rb +3 -3
- data/lib/action_controller/metal/allow_browser.rb +12 -2
- data/lib/action_controller/metal/http_authentication.rb +1 -4
- data/lib/action_controller/metal/rate_limiting.rb +1 -1
- data/lib/action_controller/metal/renderers.rb +0 -2
- data/lib/action_controller/metal/strong_parameters.rb +3 -16
- data/lib/action_controller/railtie.rb +0 -6
- data/lib/action_controller/test_case.rb +2 -2
- data/lib/action_dispatch/http/content_security_policy.rb +4 -8
- data/lib/action_dispatch/http/filter_parameters.rb +9 -4
- data/lib/action_dispatch/http/filter_redirect.rb +9 -2
- data/lib/action_dispatch/http/param_builder.rb +186 -0
- data/lib/action_dispatch/http/param_error.rb +26 -0
- data/lib/action_dispatch/http/query_parser.rb +53 -0
- data/lib/action_dispatch/http/request.rb +56 -14
- data/lib/action_dispatch/journey/scanner.rb +5 -1
- data/lib/action_dispatch/middleware/debug_view.rb +0 -5
- data/lib/action_dispatch/middleware/exception_wrapper.rb +0 -6
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +0 -3
- data/lib/action_dispatch/railtie.rb +6 -0
- data/lib/action_dispatch/request/utils.rb +9 -3
- data/lib/action_dispatch/routing/mapper.rb +65 -45
- data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -2
- data/lib/action_dispatch/routing/route_set.rb +2 -2
- data/lib/action_dispatch/testing/assertions/response.rb +12 -2
- data/lib/action_dispatch/testing/assertions/routing.rb +4 -4
- data/lib/action_dispatch/testing/integration.rb +11 -1
- data/lib/action_dispatch.rb +6 -0
- data/lib/action_pack/gem_version.rb +1 -1
- metadata +14 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 76724412ee5fbe34b92080713c9e9fab617fe1a81c4400ba2ee252d55020a6e3
|
4
|
+
data.tar.gz: 14b34e7e8e188f66b7f7da1542301fa5856fb0b14cb6213282c07c30fffbd76f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
@@ -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
|
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
|
-
# <%=
|
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(
|
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
|
33
|
+
# them an explicit name via the `name:` option.
|
34
34
|
#
|
35
35
|
# Examples:
|
36
36
|
#
|
@@ -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
|
-
#
|
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
|
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.
|
75
|
-
|
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.
|
41
|
-
|
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
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
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
|
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
|
-
|
408
|
-
|
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
|
-
|
411
|
-
|
412
|
-
self.request_parameters = Request::Utils.normalize_encode_params(pr)
|
428
|
+
|
429
|
+
self.request_parameters = pr
|
413
430
|
end
|
414
|
-
rescue
|
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", ">= 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.
|
87
|
-
return params unless
|
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
|
-
|
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
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
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
|
-
|
386
|
-
|
384
|
+
cleaned_path = Mapper.backtrace_cleaner.clean_frame(location.path)
|
385
|
+
next if cleaned_path.nil?
|
387
386
|
|
388
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
# `
|
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
|
-
#
|
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
|
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
|
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
|
-
|
91
|
-
|
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
|
-
|
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)
|
data/lib/action_dispatch.rb
CHANGED
@@ -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
|
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.
|
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-
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
351
|
-
documentation_uri: https://api.rubyonrails.org/v8.0.0.
|
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.
|
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: []
|