actionpack 7.2.2.1 → 8.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +123 -109
  3. data/lib/abstract_controller/rendering.rb +0 -1
  4. data/lib/action_controller/base.rb +1 -1
  5. data/lib/action_controller/form_builder.rb +3 -3
  6. data/lib/action_controller/metal/allow_browser.rb +11 -1
  7. data/lib/action_controller/metal/conditional_get.rb +5 -1
  8. data/lib/action_controller/metal/data_streaming.rb +4 -2
  9. data/lib/action_controller/metal/instrumentation.rb +1 -2
  10. data/lib/action_controller/metal/live.rb +13 -4
  11. data/lib/action_controller/metal/rate_limiting.rb +13 -4
  12. data/lib/action_controller/metal/redirecting.rb +2 -1
  13. data/lib/action_controller/metal/renderers.rb +2 -3
  14. data/lib/action_controller/metal/streaming.rb +5 -84
  15. data/lib/action_controller/metal/strong_parameters.rb +277 -89
  16. data/lib/action_controller/railtie.rb +1 -7
  17. data/lib/action_controller/test_case.rb +4 -2
  18. data/lib/action_dispatch/http/cache.rb +27 -10
  19. data/lib/action_dispatch/http/content_security_policy.rb +1 -0
  20. data/lib/action_dispatch/http/param_builder.rb +186 -0
  21. data/lib/action_dispatch/http/param_error.rb +26 -0
  22. data/lib/action_dispatch/http/permissions_policy.rb +2 -0
  23. data/lib/action_dispatch/http/query_parser.rb +53 -0
  24. data/lib/action_dispatch/http/request.rb +60 -16
  25. data/lib/action_dispatch/journey/parser.rb +99 -196
  26. data/lib/action_dispatch/journey/scanner.rb +44 -42
  27. data/lib/action_dispatch/middleware/cookies.rb +4 -2
  28. data/lib/action_dispatch/middleware/debug_exceptions.rb +16 -3
  29. data/lib/action_dispatch/middleware/debug_view.rb +0 -5
  30. data/lib/action_dispatch/middleware/exception_wrapper.rb +0 -6
  31. data/lib/action_dispatch/middleware/request_id.rb +2 -1
  32. data/lib/action_dispatch/middleware/ssl.rb +13 -3
  33. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +0 -3
  34. data/lib/action_dispatch/railtie.rb +8 -0
  35. data/lib/action_dispatch/request/session.rb +1 -0
  36. data/lib/action_dispatch/request/utils.rb +9 -3
  37. data/lib/action_dispatch/routing/inspector.rb +1 -1
  38. data/lib/action_dispatch/routing/mapper.rb +90 -62
  39. data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -2
  40. data/lib/action_dispatch/routing/route_set.rb +20 -8
  41. data/lib/action_dispatch/system_testing/browser.rb +12 -21
  42. data/lib/action_dispatch/testing/assertions/response.rb +12 -2
  43. data/lib/action_dispatch/testing/assertions/routing.rb +4 -4
  44. data/lib/action_dispatch/testing/integration.rb +11 -1
  45. data/lib/action_dispatch/testing/test_process.rb +1 -2
  46. data/lib/action_dispatch.rb +6 -4
  47. data/lib/action_pack/gem_version.rb +4 -4
  48. metadata +15 -34
  49. data/lib/action_dispatch/journey/parser.y +0 -50
  50. data/lib/action_dispatch/journey/parser_extras.rb +0 -33
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e2f850764c42d33756dafc52b3a241cd1264cf780ef17f52b9b3b0a8b1c3d98e
4
- data.tar.gz: 7febf80d5ab5a57de20b9658daaa10fb21216b30590837e66ceb43cb6cdfe38f
3
+ metadata.gz: 7e4dddbd1aa72f74822805435b23f5a92c79410529fc9e27048cb00d5092a612
4
+ data.tar.gz: 656ce52971952bc500713fe2892a980426ba65612907138c4ca0159951dbf338
5
5
  SHA512:
6
- metadata.gz: 6cd119f952b01a8fdf78c1a3c364bf5e681b6b0de52758a1830b935362bc7c0c9950d371bd6b6667e49dc49e8b9f98d0f60b06781a155bcf752be705e19c875f
7
- data.tar.gz: 15339819a72191cd86e77924f9a108ec6c9f7bcc7f3169ba2127bc1ccdefc2a7fdb98609689a7271faf467664df7841f163610445294dbe8eed08c48c431aa01
6
+ metadata.gz: 814ef02acc2f6218c64045ba3e000fc379657b4cea039991a0fabe96792b0f77a9d843ba9592d04156c346d51b92c91535f4f9e7cde014ee03ffebe20e9292f7
7
+ data.tar.gz: 6568066218b50285be5baac02247119c5d296751e5f437ba6efe0131630f68cfe767656e0e7da485529747f562bebeb7282a8fa4d5a25e473af787da2a6e0c72
data/CHANGELOG.md CHANGED
@@ -1,191 +1,205 @@
1
- ## Rails 7.2.2.1 (December 10, 2024) ##
1
+ ## Rails 8.0.1 (December 13, 2024) ##
2
2
 
3
- * Add validation to content security policies to disallow spaces and semicolons.
4
- Developers should use multiple arguments, and different directive methods instead.
3
+ * Add `ActionDispatch::Request::Session#store` method to conform Rack spec.
5
4
 
6
- [CVE-2024-54133]
5
+ *Yaroslav*
7
6
 
8
- *Gannon McGibbon*
9
7
 
8
+ ## Rails 8.0.0.1 (December 10, 2024) ##
10
9
 
11
- ## Rails 7.2.2 (October 30, 2024) ##
10
+ * Add validation to content security policies to disallow spaces and semicolons.
11
+ Developers should use multiple arguments, and different directive methods instead.
12
12
 
13
- * Fix non-GET requests not updating cookies in `ActionController::TestCase`.
13
+ [CVE-2024-54133]
14
14
 
15
- *Jon Moss*, *Hartley McGuire*
15
+ *Gannon McGibbon*
16
16
 
17
17
 
18
- ## Rails 7.2.1.2 (October 23, 2024) ##
18
+ ## Rails 8.0.0 (November 07, 2024) ##
19
19
 
20
20
  * No changes.
21
21
 
22
22
 
23
- ## Rails 7.2.1.1 (October 15, 2024) ##
23
+ ## Rails 8.0.0.rc2 (October 30, 2024) ##
24
24
 
25
- * Avoid regex backtracking in HTTP Token authentication
25
+ * Fix routes with `::` in the path.
26
26
 
27
- [CVE-2024-47887]
27
+ *Rafael Mendonça França*
28
28
 
29
- *John Hawthorn*
29
+ * Maintain Rack 2 parameter parsing behaviour.
30
30
 
31
- * Avoid regex backtracking in query parameter filtering
31
+ *Matthew Draper*
32
32
 
33
- [CVE-2024-41128]
34
33
 
35
- *John Hawthorn*
34
+ ## Rails 8.0.0.rc1 (October 19, 2024) ##
36
35
 
36
+ * Remove `Rails.application.config.action_controller.allow_deprecated_parameters_hash_equality`.
37
37
 
38
- ## Rails 7.2.1 (August 22, 2024) ##
38
+ *Rafael Mendonça França*
39
39
 
40
- * Fix `Request#raw_post` raising `NoMethodError` when `rack.input` is `nil`.
40
+ * Improve `ActionController::TestCase` to expose a binary encoded `request.body`.
41
41
 
42
- *Hartley McGuire*
42
+ The rack spec clearly states:
43
43
 
44
+ > The input stream is an IO-like object which contains the raw HTTP POST data.
45
+ > When applicable, its external encoding must be “ASCII-8BIT” and it must be opened in binary mode.
44
46
 
45
- ## Rails 7.2.0 (August 09, 2024) ##
47
+ Until now its encoding was generally UTF-8, which doesn't accurately reflect production
48
+ behavior.
46
49
 
47
- * Allow bots to ignore `allow_browser`.
50
+ *Jean Boussier*
48
51
 
49
- *Matthew Nguyen*
52
+ * Update `ActionController::AllowBrowser` to support passing method names to `:block`
50
53
 
51
- * Include the HTTP Permissions-Policy on non-HTML Content-Types
52
- [CVE-2024-28103]
53
-
54
- *Aaron Patterson*, *Zack Deveau*
54
+ ```ruby
55
+ class ApplicationController < ActionController::Base
56
+ allow_browser versions: :modern, block: :handle_outdated_browser
55
57
 
56
- * Fix `Mime::Type.parse` handling type parameters for HTTP Accept headers.
58
+ private
59
+ def handle_outdated_browser
60
+ render file: Rails.root.join("public/custom-error.html"), status: :not_acceptable
61
+ end
62
+ end
63
+ ```
57
64
 
58
- *Taylor Chaparro*
65
+ *Sean Doyle*
59
66
 
60
- * Fix the error page that is displayed when a view template is missing to account for nested controller paths in the
61
- suggested correct location for the missing template.
67
+ * Raise an `ArgumentError` when invalid `:only` or `:except` options are passed into `#resource` and `#resources`.
62
68
 
63
69
  *Joshua Young*
64
70
 
65
- * Add `save_and_open_page` helper to `IntegrationTest`.
66
-
67
- `save_and_open_page` is a helpful helper to keep a short feedback loop when working on system tests.
68
- A similar helper with matching signature has been added to integration tests.
69
-
70
- *Joé Dupuis*
71
-
72
- * Fix a regression in 7.1.3 passing a `to:` option without a controller when the controller is already defined by a scope.
71
+ ## Rails 8.0.0.beta1 (September 26, 2024) ##
73
72
 
74
- ```ruby
75
- Rails.application.routes.draw do
76
- controller :home do
77
- get "recent", to: "recent_posts"
78
- end
79
- end
80
- ```
81
-
82
- *Étienne Barrié*
83
-
84
- * Request Forgery takes relative paths into account.
73
+ * Fix non-GET requests not updating cookies in `ActionController::TestCase`.
85
74
 
86
- *Stefan Wienert*
75
+ *Jon Moss*, *Hartley McGuire*
87
76
 
88
- * Add ".test" as a default allowed host in development to ensure smooth golden-path setup with puma.dev.
77
+ * Update `ActionController::Live` to use a thread-pool to reuse threads across requests.
89
78
 
90
- *DHH*
79
+ *Adam Renberg Tamm*
91
80
 
92
- * Add `allow_browser` to set minimum browser versions for the application.
81
+ * Introduce safer, more explicit params handling method with `params#expect` such that
82
+ `params.expect(table: [ :attr ])` replaces `params.require(:table).permit(:attr)`
93
83
 
94
- A browser that's blocked will by default be served the file in `public/406-unsupported-browser.html` with a HTTP status code of "406 Not Acceptable".
84
+ Ensures params are filtered with consideration for the expected
85
+ types of values, improving handling of params and avoiding ignorable
86
+ errors caused by params tampering.
95
87
 
96
88
  ```ruby
97
- class ApplicationController < ActionController::Base
98
- # Allow only browsers natively supporting webp images, web push, badges, import maps, CSS nesting + :has
99
- allow_browser versions: :modern
100
- end
89
+ # If the url is altered to ?person=hacked
90
+ # Before
91
+ params.require(:person).permit(:name, :age, pets: [:name])
92
+ # raises NoMethodError, causing a 500 and potential error reporting
93
+
94
+ # After
95
+ params.expect(person: [ :name, :age, pets: [[:name]] ])
96
+ # raises ActionController::ParameterMissing, correctly returning a 400 error
97
+ ```
101
98
 
102
- class ApplicationController < ActionController::Base
103
- # 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+.
104
- allow_browser versions: { safari: 16.4, firefox: 121, ie: false }
105
- end
99
+ You may also notice the new double array `[[:name]]`. In order to
100
+ declare when a param is expected to be an array of parameter hashes,
101
+ this new double array syntax is used to explicitly declare an array.
102
+ `expect` requires you to declare expected arrays in this way, and will
103
+ ignore arrays that are passed when, for example, `pet: [:name]` is used.
106
104
 
107
- class MessagesController < ApplicationController
108
- # In addition to the browsers blocked by ApplicationController, also block Opera below 104 and Chrome below 119 for the show action.
109
- allow_browser versions: { opera: 104, chrome: 119 }, only: :show
110
- end
111
- ```
105
+ In order to preserve compatibility, `permit` does not adopt the new
106
+ double array syntax and is therefore more permissive about unexpected
107
+ types. Using `expect` everywhere is recommended.
112
108
 
113
- *DHH*
109
+ We suggest replacing `params.require(:person).permit(:name, :age)`
110
+ with the direct replacement `params.expect(person: [:name, :age])`
111
+ to prevent external users from manipulating params to trigger 500
112
+ errors. A 400 error will be returned instead, using public/400.html
114
113
 
115
- * Add rate limiting API.
114
+ Usage of `params.require(:id)` should likewise be replaced with
115
+ `params.expect(:id)` which is designed to ensure that `params[:id]`
116
+ is a scalar and not an array or hash, also requiring the param.
116
117
 
117
118
  ```ruby
118
- class SessionsController < ApplicationController
119
- rate_limit to: 10, within: 3.minutes, only: :create
120
- end
119
+ # Before
120
+ User.find(params.require(:id)) # allows an array, altering behavior
121
121
 
122
- class SignupsController < ApplicationController
123
- rate_limit to: 1000, within: 10.seconds,
124
- by: -> { request.domain }, with: -> { redirect_to busy_controller_url, alert: "Too many signups!" }, only: :new
125
- end
122
+ # After
123
+ User.find(params.expect(:id)) # expect only returns non-blank permitted scalars (excludes Hash, Array, nil, "", etc)
126
124
  ```
127
125
 
128
- *DHH*, *Jean Boussier*
126
+ *Martin Emde*
127
+
128
+ * System Testing: Disable Chrome's search engine choice by default in system tests.
129
129
 
130
- * Add `image/svg+xml` to the compressible content types of `ActionDispatch::Static`.
130
+ *glaszig*
131
131
 
132
- *Georg Ledermann*
132
+ * Fix `Request#raw_post` raising `NoMethodError` when `rack.input` is `nil`.
133
133
 
134
- * Add instrumentation for `ActionController::Live#send_stream`.
134
+ *Hartley McGuire*
135
135
 
136
- Allows subscribing to `send_stream` events. The event payload contains the filename, disposition, and type.
136
+ * Remove `racc` dependency by manually writing `ActionDispatch::Journey::Scanner`.
137
137
 
138
- *Hannah Ramadan*
138
+ *Gannon McGibbon*
139
139
 
140
- * Add support for `with_routing` test helper in `ActionDispatch::IntegrationTest`.
140
+ * Speed up `ActionDispatch::Routing::Mapper::Scope#[]` by merging frame hashes.
141
141
 
142
142
  *Gannon McGibbon*
143
143
 
144
- * Remove deprecated support to set `Rails.application.config.action_dispatch.show_exceptions` to `true` and `false`.
144
+ * Allow bots to ignore `allow_browser`.
145
145
 
146
- *Rafael Mendonça França*
146
+ *Matthew Nguyen*
147
147
 
148
- * Remove deprecated `speaker`, `vibrate`, and `vr` permissions policy directives.
148
+ * Deprecate drawing routes with multiple paths to make routing faster.
149
+ You may use `with_options` or a loop to make drawing multiple paths easier.
149
150
 
150
- *Rafael Mendonça França*
151
+ ```ruby
152
+ # Before
153
+ get "/users", "/other_path", to: "users#index"
151
154
 
152
- * Remove deprecated `Rails.application.config.action_dispatch.return_only_request_media_type_on_content_type`.
155
+ # After
156
+ get "/users", to: "users#index"
157
+ get "/other_path", to: "users#index"
158
+ ```
153
159
 
154
- *Rafael Mendonça França*
160
+ *Gannon McGibbon*
155
161
 
156
- * Deprecate `Rails.application.config.action_controller.allow_deprecated_parameters_hash_equality`.
162
+ * Make `http_cache_forever` use `immutable: true`
157
163
 
158
- *Rafael Mendonça França*
164
+ *Nate Matykiewicz*
159
165
 
160
- * Remove deprecated comparison between `ActionController::Parameters` and `Hash`.
166
+ * Add `config.action_dispatch.strict_freshness`.
161
167
 
162
- *Rafael Mendonça França*
168
+ When set to `true`, the `ETag` header takes precedence over the `Last-Modified` header when both are present,
169
+ as specified by RFC 7232, Section 6.
163
170
 
164
- * Remove deprecated constant `AbstractController::Helpers::MissingHelperError`.
171
+ Defaults to `false` to maintain compatibility with previous versions of Rails, but is enabled as part of
172
+ Rails 8.0 defaults.
165
173
 
166
- *Rafael Mendonça França*
174
+ *heka1024*
167
175
 
168
- * Fix a race condition that could cause a `Text file busy - chromedriver`
169
- error with parallel system tests.
176
+ * Support `immutable` directive in Cache-Control
170
177
 
171
- *Matt Brictson*
178
+ ```ruby
179
+ expires_in 1.minute, public: true, immutable: true
180
+ # Cache-Control: public, max-age=60, immutable
181
+ ```
172
182
 
173
- * Add `racc` as a dependency since it will become a bundled gem in Ruby 3.4.0
183
+ *heka1024*
174
184
 
175
- *Hartley McGuire*
176
- * Remove deprecated constant `ActionDispatch::IllegalStateError`.
185
+ * Add `:wasm_unsafe_eval` mapping for `content_security_policy`
177
186
 
178
- *Rafael Mendonça França*
187
+ ```ruby
188
+ # Before
189
+ policy.script_src "'wasm-unsafe-eval'"
190
+
191
+ # After
192
+ policy.script_src :wasm_unsafe_eval
193
+ ```
179
194
 
180
- * Add parameter filter capability for redirect locations.
195
+ *Joe Haig*
181
196
 
182
- It uses the `config.filter_parameters` to match what needs to be filtered.
183
- The result would be like this:
197
+ * Add `display_capture` and `keyboard_map` in `permissions_policy`
184
198
 
185
- Redirected to http://secret.foo.bar?username=roque&password=[FILTERED]
199
+ *Cyril Blaecke*
186
200
 
187
- Fixes #14055.
201
+ * Add `connect` route helper.
188
202
 
189
- *Roque Pinel*, *Trevor Turk*, *tonytonyjan*
203
+ *Samuel Williams*
190
204
 
191
- Please check [7-1-stable](https://github.com/rails/rails/blob/7-1-stable/actionpack/CHANGELOG.md) for previous changes.
205
+ Please check [7-2-stable](https://github.com/rails/rails/blob/7-2-stable/actionpack/CHANGELOG.md) for previous changes.
@@ -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
@@ -266,7 +266,7 @@ module ActionController
266
266
  ParamsWrapper
267
267
  ]
268
268
 
269
- # Note: Documenting these severely degrates the performance of rdoc
269
+ # Note: Documenting these severely degrades the performance of rdoc
270
270
  # :stopdoc:
271
271
  include AbstractController::Rendering
272
272
  include AbstractController::Translation
@@ -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,7 +65,7 @@ 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
@@ -259,6 +259,9 @@ module ActionController
259
259
  # `:stale_if_error`
260
260
  # : Sets the value of the `stale-if-error` directive.
261
261
  #
262
+ # `:immutable`
263
+ # : If true, adds the `immutable` directive.
264
+ #
262
265
  #
263
266
  # Any additional key-value pairs are concatenated as directives. For a list of
264
267
  # supported `Cache-Control` directives, see the [article on
@@ -292,6 +295,7 @@ module ActionController
292
295
  must_revalidate: options.delete(:must_revalidate),
293
296
  stale_while_revalidate: options.delete(:stale_while_revalidate),
294
297
  stale_if_error: options.delete(:stale_if_error),
298
+ immutable: options.delete(:immutable),
295
299
  )
296
300
  options.delete(:private)
297
301
 
@@ -315,7 +319,7 @@ module ActionController
315
319
  # user's web browser. To allow proxies to cache the response, set `true` to
316
320
  # indicate that they can serve the cached response to all users.
317
321
  def http_cache_forever(public: false)
318
- expires_in 100.years, public: public
322
+ expires_in 100.years, public: public, immutable: true
319
323
 
320
324
  yield if stale?(etag: request.fullpath,
321
325
  last_modified: Time.new(2011, 1, 1).utc,
@@ -28,7 +28,8 @@ module ActionController # :nodoc:
28
28
  # `send_file(params[:path])` allows a malicious user to download any file on
29
29
  # your server.
30
30
  #
31
- # Options:
31
+ # #### Options:
32
+ #
32
33
  # * `:filename` - suggests a filename for the browser to use. Defaults to
33
34
  # `File.basename(path)`.
34
35
  # * `:type` - specifies an HTTP content type. You can specify either a string
@@ -90,7 +91,8 @@ module ActionController # :nodoc:
90
91
  # inline data. You may also set the content type, the file name, and other
91
92
  # things.
92
93
  #
93
- # Options:
94
+ # #### Options:
95
+ #
94
96
  # * `:filename` - suggests a filename for the browser to use.
95
97
  # * `:type` - specifies an HTTP content type. Defaults to
96
98
  # `application/octet-stream`. You can specify either a string or a symbol
@@ -2,7 +2,6 @@
2
2
 
3
3
  # :markup: markdown
4
4
 
5
- require "benchmark"
6
5
  require "abstract_controller/logger"
7
6
 
8
7
  module ActionController
@@ -29,7 +28,7 @@ module ActionController
29
28
  def render(*)
30
29
  render_output = nil
31
30
  self.view_runtime = cleanup_view_runtime do
32
- Benchmark.ms { render_output = super }
31
+ ActiveSupport::Benchmark.realtime(:float_millisecond) { render_output = super }
33
32
  end
34
33
  render_output
35
34
  end
@@ -58,7 +58,7 @@ module ActionController
58
58
 
59
59
  module ClassMethods
60
60
  def make_response!(request)
61
- if request.get_header("HTTP_VERSION") == "HTTP/1.0"
61
+ if (request.get_header("SERVER_PROTOCOL") || request.get_header("HTTP_VERSION")) == "HTTP/1.0"
62
62
  super
63
63
  else
64
64
  Live::Response.new.tap do |res|
@@ -307,6 +307,10 @@ module ActionController
307
307
  error = e
308
308
  end
309
309
  ensure
310
+ # Ensure we clean up any thread locals we copied so that the thread can reused.
311
+ ActiveSupport::IsolatedExecutionState.clear
312
+ locals.each { |k, _| t2[k] = nil }
313
+
310
314
  @_response.commit!
311
315
  end
312
316
  end
@@ -328,7 +332,8 @@ module ActionController
328
332
  # or other running data where you don't want the entire file buffered in memory
329
333
  # first. Similar to send_data, but where the data is generated live.
330
334
  #
331
- # Options:
335
+ # #### Options:
336
+ #
332
337
  # * `:filename` - suggests a filename for the browser to use.
333
338
  # * `:type` - specifies an HTTP content type. You can specify either a string
334
339
  # or a symbol for a registered type with `Mime::Type.register`, for example
@@ -371,11 +376,15 @@ module ActionController
371
376
  # data from the response bodies. Nobody should call this method except in Rails
372
377
  # internals. Seriously!
373
378
  def new_controller_thread # :nodoc:
374
- Thread.new {
379
+ ActionController::Live.live_thread_pool_executor.post do
375
380
  t2 = Thread.current
376
381
  t2.abort_on_exception = true
377
382
  yield
378
- }
383
+ end
384
+ end
385
+
386
+ def self.live_thread_pool_executor
387
+ @live_thread_pool_executor ||= Concurrent::CachedThreadPool.new(name: "action_controller.live")
379
388
  end
380
389
 
381
390
  def log_error(exception)
@@ -29,6 +29,9 @@ module ActionController # :nodoc:
29
29
  # datastore as your general caches, you can pass a custom store in the `store`
30
30
  # parameter.
31
31
  #
32
+ # If you want to use multiple rate limits per controller, you need to give each of
33
+ # them an explicit name via the `name:` option.
34
+ #
32
35
  # Examples:
33
36
  #
34
37
  # class SessionsController < ApplicationController
@@ -44,14 +47,20 @@ module ActionController # :nodoc:
44
47
  # RATE_LIMIT_STORE = ActiveSupport::Cache::RedisCacheStore.new(url: ENV["REDIS_URL"])
45
48
  # rate_limit to: 10, within: 3.minutes, store: RATE_LIMIT_STORE
46
49
  # end
47
- def rate_limit(to:, within:, by: -> { request.remote_ip }, with: -> { head :too_many_requests }, store: cache_store, **options)
48
- before_action -> { rate_limiting(to: to, within: within, by: by, with: with, store: store) }, **options
50
+ #
51
+ # class SessionsController < ApplicationController
52
+ # rate_limit to: 3, within: 2.seconds, name: "short-term"
53
+ # rate_limit to: 10, within: 5.minutes, name: "long-term"
54
+ # end
55
+ def rate_limit(to:, within:, by: -> { request.remote_ip }, with: -> { head :too_many_requests }, store: cache_store, name: nil, **options)
56
+ before_action -> { rate_limiting(to: to, within: within, by: by, with: with, store: store, name: name) }, **options
49
57
  end
50
58
  end
51
59
 
52
60
  private
53
- def rate_limiting(to:, within:, by:, with:, store:)
54
- count = store.increment("rate-limit:#{controller_path}:#{instance_exec(&by)}", 1, expires_in: within)
61
+ def rate_limiting(to:, within:, by:, with:, store:, name:)
62
+ cache_key = ["rate-limit", controller_path, name, instance_exec(&by)].compact.join(":")
63
+ count = store.increment(cache_key, 1, expires_in: within)
55
64
  if count && count > to
56
65
  ActiveSupport::Notifications.instrument("rate_limit.action_controller", request: request) do
57
66
  instance_exec(&with)
@@ -106,13 +106,14 @@ module ActionController
106
106
 
107
107
  allow_other_host = response_options.delete(:allow_other_host) { _allow_other_host }
108
108
 
109
- self.status = _extract_redirect_to_status(options, response_options)
109
+ proposed_status = _extract_redirect_to_status(options, response_options)
110
110
 
111
111
  redirect_to_location = _compute_redirect_to_location(request, options)
112
112
  _ensure_url_is_http_header_safe(redirect_to_location)
113
113
 
114
114
  self.location = _enforce_open_redirect_protection(redirect_to_location, allow_other_host: allow_other_host)
115
115
  self.response_body = ""
116
+ self.status = proposed_status
116
117
  end
117
118
 
118
119
  # Soft deprecated alias for #redirect_back_or_to where the `fallback_location`
@@ -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)
@@ -154,7 +152,8 @@ module ActionController
154
152
  end
155
153
 
156
154
  add :json do |json, options|
157
- json = json.to_json(options) unless json.kind_of?(String)
155
+ json_options = options.except(:callback, :content_type, :status)
156
+ json = json.to_json(json_options) unless json.kind_of?(String)
158
157
 
159
158
  if options[:callback].present?
160
159
  if media_type.nil? || media_type == Mime[:json]