actionpack 7.2.1.1 → 8.0.0.rc1

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.

Potentially problematic release.


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

Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +108 -100
  3. data/lib/abstract_controller/helpers.rb +2 -0
  4. data/lib/abstract_controller/rendering.rb +0 -1
  5. data/lib/action_controller/api.rb +1 -0
  6. data/lib/action_controller/form_builder.rb +3 -3
  7. data/lib/action_controller/metal/allow_browser.rb +12 -2
  8. data/lib/action_controller/metal/conditional_get.rb +6 -3
  9. data/lib/action_controller/metal/http_authentication.rb +6 -3
  10. data/lib/action_controller/metal/instrumentation.rb +1 -2
  11. data/lib/action_controller/metal/live.rb +19 -8
  12. data/lib/action_controller/metal/rate_limiting.rb +13 -4
  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 +274 -88
  16. data/lib/action_controller/railtie.rb +1 -7
  17. data/lib/action_controller/test_case.rb +6 -5
  18. data/lib/action_dispatch/http/cache.rb +27 -10
  19. data/lib/action_dispatch/http/content_security_policy.rb +5 -8
  20. data/lib/action_dispatch/http/filter_parameters.rb +4 -9
  21. data/lib/action_dispatch/http/filter_redirect.rb +2 -9
  22. data/lib/action_dispatch/http/param_builder.rb +163 -0
  23. data/lib/action_dispatch/http/param_error.rb +26 -0
  24. data/lib/action_dispatch/http/permissions_policy.rb +2 -0
  25. data/lib/action_dispatch/http/query_parser.rb +31 -0
  26. data/lib/action_dispatch/http/request.rb +60 -16
  27. data/lib/action_dispatch/journey/parser.rb +99 -196
  28. data/lib/action_dispatch/journey/scanner.rb +40 -42
  29. data/lib/action_dispatch/middleware/cookies.rb +4 -2
  30. data/lib/action_dispatch/middleware/debug_exceptions.rb +16 -3
  31. data/lib/action_dispatch/middleware/debug_view.rb +0 -5
  32. data/lib/action_dispatch/middleware/exception_wrapper.rb +0 -6
  33. data/lib/action_dispatch/middleware/remote_ip.rb +5 -6
  34. data/lib/action_dispatch/middleware/request_id.rb +2 -1
  35. data/lib/action_dispatch/middleware/ssl.rb +14 -4
  36. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +0 -3
  37. data/lib/action_dispatch/railtie.rb +2 -0
  38. data/lib/action_dispatch/request/utils.rb +9 -3
  39. data/lib/action_dispatch/routing/inspector.rb +1 -1
  40. data/lib/action_dispatch/routing/mapper.rb +91 -62
  41. data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -2
  42. data/lib/action_dispatch/routing/route_set.rb +20 -8
  43. data/lib/action_dispatch/system_testing/browser.rb +12 -21
  44. data/lib/action_dispatch/testing/assertions/response.rb +12 -2
  45. data/lib/action_dispatch/testing/assertions/routing.rb +4 -4
  46. data/lib/action_dispatch/testing/integration.rb +11 -1
  47. data/lib/action_dispatch.rb +6 -0
  48. data/lib/action_pack/gem_version.rb +4 -4
  49. metadata +15 -34
  50. data/lib/action_dispatch/journey/parser.y +0 -50
  51. 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: aa5e422c3ae8a04f0fac4410c72203218277a1b813047ad3ce5aa3274eac5a0e
4
- data.tar.gz: 19620a0b88f940ae2a3183dbf20828228ae3f8fb567d32e715ba92fd9ea29db8
3
+ metadata.gz: cbf30070e8c7658bcda189e9c07184b10bfdeb1ae28ae9b7c6e354189e96eac6
4
+ data.tar.gz: c21576442cf2c2e3ef1cf7e36f5e412349b6f933c651cca641a9afe48229cd96
5
5
  SHA512:
6
- metadata.gz: 27b3549782fe4594a04571356362c11a03a1bf9e368fbf49662a1514cc170ec01f85be5085ffe77e62e57435702637db27f0502c118b7084d90d05f00e0d7a1a
7
- data.tar.gz: d8ba844e9b95cf76ebcbc899678b2752f5f3ddbf3cb81a4ac94c86ed1f4bb5d3fd471f701e69779f9aee99bbd0612f5aa2ddf79e75d32735aab436f58ef99cb7
6
+ metadata.gz: 8d09731d99912ded6338f7a0fcc0d98706efbf4721f26d35edcede064e240d607f6ddc5ed43a979ebe3c8d6c1e9b90347d84725c6b504c91c79fb9821edca478
7
+ data.tar.gz: 5b981e0db05e7d35cda56797acbf513050fd099bb3778ec2247012e1a338dec2b1fe608a6653a4dbe54c621de63f576588b98b8d77b172f9456b78e2e9a7cd9b
data/CHANGELOG.md CHANGED
@@ -1,164 +1,172 @@
1
- ## Rails 7.2.1.1 (October 15, 2024) ##
1
+ ## Rails 8.0.0.rc1 (October 19, 2024) ##
2
2
 
3
- * Avoid regex backtracking in HTTP Token authentication
3
+ * Remove `Rails.application.config.action_controller.allow_deprecated_parameters_hash_equality`.
4
4
 
5
- [CVE-2024-47887]
6
-
7
- * Avoid regex backtracking in query parameter filtering
5
+ *Rafael Mendonça França*
8
6
 
9
- [CVE-2024-41128]
7
+ * Improve `ActionController::TestCase` to expose a binary encoded `request.body`.
10
8
 
11
- ## Rails 7.2.1 (August 22, 2024) ##
9
+ The rack spec clearly states:
12
10
 
13
- * Fix `Request#raw_post` raising `NoMethodError` when `rack.input` is `nil`.
11
+ > The input stream is an IO-like object which contains the raw HTTP POST data.
12
+ > When applicable, its external encoding must be “ASCII-8BIT” and it must be opened in binary mode.
14
13
 
15
- *Hartley McGuire*
14
+ Until now its encoding was generally UTF-8, which doesn't accurately reflect production
15
+ behavior.
16
16
 
17
+ *Jean Boussier*
17
18
 
18
- ## Rails 7.2.0 (August 09, 2024) ##
19
+ * Update `ActionController::AllowBrowser` to support passing method names to `:block`
19
20
 
20
- * Allow bots to ignore `allow_browser`.
21
+ ```ruby
22
+ class ApplicationController < ActionController::Base
23
+ allow_browser versions: :modern, block: :handle_outdated_browser
21
24
 
22
- *Matthew Nguyen*
25
+ private
26
+ def handle_outdated_browser
27
+ render file: Rails.root.join("public/custom-error.html"), status: :not_acceptable
28
+ end
29
+ end
30
+ ```
23
31
 
24
- * Include the HTTP Permissions-Policy on non-HTML Content-Types
25
- [CVE-2024-28103]
32
+ *Sean Doyle*
26
33
 
27
- *Aaron Patterson*, *Zack Deveau*
34
+ * Raise an `ArgumentError` when invalid `:only` or `:except` options are passed into `#resource` and `#resources`.
28
35
 
29
- * Fix `Mime::Type.parse` handling type parameters for HTTP Accept headers.
36
+ *Joshua Young*
30
37
 
31
- *Taylor Chaparro*
38
+ ## Rails 8.0.0.beta1 (September 26, 2024) ##
32
39
 
33
- * Fix the error page that is displayed when a view template is missing to account for nested controller paths in the
34
- suggested correct location for the missing template.
40
+ * Fix non-GET requests not updating cookies in `ActionController::TestCase`.
35
41
 
36
- *Joshua Young*
42
+ *Jon Moss*, *Hartley McGuire*
37
43
 
38
- * Add `save_and_open_page` helper to `IntegrationTest`.
44
+ * Update `ActionController::Live` to use a thread-pool to reuse threads across requests.
39
45
 
40
- `save_and_open_page` is a helpful helper to keep a short feedback loop when working on system tests.
41
- A similar helper with matching signature has been added to integration tests.
46
+ *Adam Renberg Tamm*
42
47
 
43
- *Joé Dupuis*
48
+ * Introduce safer, more explicit params handling method with `params#expect` such that
49
+ `params.expect(table: [ :attr ])` replaces `params.require(:table).permit(:attr)`
44
50
 
45
- * Fix a regression in 7.1.3 passing a `to:` option without a controller when the controller is already defined by a scope.
51
+ Ensures params are filtered with consideration for the expected
52
+ types of values, improving handling of params and avoiding ignorable
53
+ errors caused by params tampering.
46
54
 
47
55
  ```ruby
48
- Rails.application.routes.draw do
49
- controller :home do
50
- get "recent", to: "recent_posts"
51
- end
52
- end
56
+ # If the url is altered to ?person=hacked
57
+ # Before
58
+ params.require(:person).permit(:name, :age, pets: [:name])
59
+ # raises NoMethodError, causing a 500 and potential error reporting
60
+
61
+ # After
62
+ params.expect(person: [ :name, :age, pets: [[:name]] ])
63
+ # raises ActionController::ParameterMissing, correctly returning a 400 error
53
64
  ```
54
65
 
55
- *Étienne Barrié*
66
+ You may also notice the new double array `[[:name]]`. In order to
67
+ declare when a param is expected to be an array of parameter hashes,
68
+ this new double array syntax is used to explicitly declare an array.
69
+ `expect` requires you to declare expected arrays in this way, and will
70
+ ignore arrays that are passed when, for example, `pet: [:name]` is used.
56
71
 
57
- * Request Forgery takes relative paths into account.
72
+ In order to preserve compatibility, `permit` does not adopt the new
73
+ double array syntax and is therefore more permissive about unexpected
74
+ types. Using `expect` everywhere is recommended.
58
75
 
59
- *Stefan Wienert*
76
+ We suggest replacing `params.require(:person).permit(:name, :age)`
77
+ with the direct replacement `params.expect(person: [:name, :age])`
78
+ to prevent external users from manipulating params to trigger 500
79
+ errors. A 400 error will be returned instead, using public/400.html
60
80
 
61
- * Add ".test" as a default allowed host in development to ensure smooth golden-path setup with puma.dev.
62
-
63
- *DHH*
64
-
65
- * Add `allow_browser` to set minimum browser versions for the application.
66
-
67
- 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".
81
+ Usage of `params.require(:id)` should likewise be replaced with
82
+ `params.expect(:id)` which is designed to ensure that `params[:id]`
83
+ is a scalar and not an array or hash, also requiring the param.
68
84
 
69
85
  ```ruby
70
- class ApplicationController < ActionController::Base
71
- # Allow only browsers natively supporting webp images, web push, badges, import maps, CSS nesting + :has
72
- allow_browser versions: :modern
73
- end
74
-
75
- class ApplicationController < ActionController::Base
76
- # 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+.
77
- allow_browser versions: { safari: 16.4, firefox: 121, ie: false }
78
- end
86
+ # Before
87
+ User.find(params.require(:id)) # allows an array, altering behavior
79
88
 
80
- class MessagesController < ApplicationController
81
- # In addition to the browsers blocked by ApplicationController, also block Opera below 104 and Chrome below 119 for the show action.
82
- allow_browser versions: { opera: 104, chrome: 119 }, only: :show
83
- end
89
+ # After
90
+ User.find(params.expect(:id)) # expect only returns non-blank permitted scalars (excludes Hash, Array, nil, "", etc)
84
91
  ```
85
92
 
86
- *DHH*
87
-
88
- * Add rate limiting API.
89
-
90
- ```ruby
91
- class SessionsController < ApplicationController
92
- rate_limit to: 10, within: 3.minutes, only: :create
93
- end
94
-
95
- class SignupsController < ApplicationController
96
- rate_limit to: 1000, within: 10.seconds,
97
- by: -> { request.domain }, with: -> { redirect_to busy_controller_url, alert: "Too many signups!" }, only: :new
98
- end
99
- ```
93
+ *Martin Emde*
100
94
 
101
- *DHH*, *Jean Boussier*
95
+ * System Testing: Disable Chrome's search engine choice by default in system tests.
102
96
 
103
- * Add `image/svg+xml` to the compressible content types of `ActionDispatch::Static`.
97
+ *glaszig*
104
98
 
105
- *Georg Ledermann*
99
+ * Fix `Request#raw_post` raising `NoMethodError` when `rack.input` is `nil`.
106
100
 
107
- * Add instrumentation for `ActionController::Live#send_stream`.
101
+ *Hartley McGuire*
108
102
 
109
- Allows subscribing to `send_stream` events. The event payload contains the filename, disposition, and type.
103
+ * Remove `racc` dependency by manually writing `ActionDispatch::Journey::Scanner`.
110
104
 
111
- *Hannah Ramadan*
105
+ *Gannon McGibbon*
112
106
 
113
- * Add support for `with_routing` test helper in `ActionDispatch::IntegrationTest`.
107
+ * Speed up `ActionDispatch::Routing::Mapper::Scope#[]` by merging frame hashes.
114
108
 
115
109
  *Gannon McGibbon*
116
110
 
117
- * Remove deprecated support to set `Rails.application.config.action_dispatch.show_exceptions` to `true` and `false`.
111
+ * Allow bots to ignore `allow_browser`.
118
112
 
119
- *Rafael Mendonça França*
113
+ *Matthew Nguyen*
120
114
 
121
- * Remove deprecated `speaker`, `vibrate`, and `vr` permissions policy directives.
115
+ * Deprecate drawing routes with multiple paths to make routing faster.
116
+ You may use `with_options` or a loop to make drawing multiple paths easier.
122
117
 
123
- *Rafael Mendonça França*
118
+ ```ruby
119
+ # Before
120
+ get "/users", "/other_path", to: "users#index"
124
121
 
125
- * Remove deprecated `Rails.application.config.action_dispatch.return_only_request_media_type_on_content_type`.
122
+ # After
123
+ get "/users", to: "users#index"
124
+ get "/other_path", to: "users#index"
125
+ ```
126
126
 
127
- *Rafael Mendonça França*
127
+ *Gannon McGibbon*
128
128
 
129
- * Deprecate `Rails.application.config.action_controller.allow_deprecated_parameters_hash_equality`.
129
+ * Make `http_cache_forever` use `immutable: true`
130
130
 
131
- *Rafael Mendonça França*
131
+ *Nate Matykiewicz*
132
132
 
133
- * Remove deprecated comparison between `ActionController::Parameters` and `Hash`.
133
+ * Add `config.action_dispatch.strict_freshness`.
134
134
 
135
- *Rafael Mendonça França*
135
+ When set to `true`, the `ETag` header takes precedence over the `Last-Modified` header when both are present,
136
+ as specified by RFC 7232, Section 6.
136
137
 
137
- * Remove deprecated constant `AbstractController::Helpers::MissingHelperError`.
138
+ Defaults to `false` to maintain compatibility with previous versions of Rails, but is enabled as part of
139
+ Rails 8.0 defaults.
138
140
 
139
- *Rafael Mendonça França*
141
+ *heka1024*
142
+
143
+ * Support `immutable` directive in Cache-Control
140
144
 
141
- * Fix a race condition that could cause a `Text file busy - chromedriver`
142
- error with parallel system tests.
145
+ ```ruby
146
+ expires_in 1.minute, public: true, immutable: true
147
+ # Cache-Control: public, max-age=60, immutable
148
+ ```
143
149
 
144
- *Matt Brictson*
150
+ *heka1024*
145
151
 
146
- * Add `racc` as a dependency since it will become a bundled gem in Ruby 3.4.0
152
+ * Add `:wasm_unsafe_eval` mapping for `content_security_policy`
147
153
 
148
- *Hartley McGuire*
149
- * Remove deprecated constant `ActionDispatch::IllegalStateError`.
154
+ ```ruby
155
+ # Before
156
+ policy.script_src "'wasm-unsafe-eval'"
150
157
 
151
- *Rafael Mendonça França*
158
+ # After
159
+ policy.script_src :wasm_unsafe_eval
160
+ ```
152
161
 
153
- * Add parameter filter capability for redirect locations.
162
+ *Joe Haig*
154
163
 
155
- It uses the `config.filter_parameters` to match what needs to be filtered.
156
- The result would be like this:
164
+ * Add `display_capture` and `keyboard_map` in `permissions_policy`
157
165
 
158
- Redirected to http://secret.foo.bar?username=roque&password=[FILTERED]
166
+ *Cyril Blaecke*
159
167
 
160
- Fixes #14055.
168
+ * Add `connect` route helper.
161
169
 
162
- *Roque Pinel*, *Trevor Turk*, *tonytonyjan*
170
+ *Samuel Williams*
163
171
 
164
- Please check [7-1-stable](https://github.com/rails/rails/blob/7-1-stable/actionpack/CHANGELOG.md) for previous changes.
172
+ Please check [7-2-stable](https://github.com/rails/rails/blob/7-2-stable/actionpack/CHANGELOG.md) for previous changes.
@@ -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
@@ -123,6 +123,7 @@ module ActionController
123
123
  BasicImplicitRender,
124
124
  StrongParameters,
125
125
  RateLimiting,
126
+ Caching,
126
127
 
127
128
  DataStreaming,
128
129
  DefaultHeaders,
@@ -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
  }
@@ -76,8 +76,7 @@ module ActionController
76
76
  # `:cache_control`
77
77
  # : When given, will overwrite an existing `Cache-Control` header. For a list
78
78
  # of `Cache-Control` directives, see the [article on
79
- # MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Contr
80
- # ol).
79
+ # MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control).
81
80
  #
82
81
  # `:template`
83
82
  # : By default, the template digest for the current controller/action is
@@ -260,6 +259,9 @@ module ActionController
260
259
  # `:stale_if_error`
261
260
  # : Sets the value of the `stale-if-error` directive.
262
261
  #
262
+ # `:immutable`
263
+ # : If true, adds the `immutable` directive.
264
+ #
263
265
  #
264
266
  # Any additional key-value pairs are concatenated as directives. For a list of
265
267
  # supported `Cache-Control` directives, see the [article on
@@ -293,6 +295,7 @@ module ActionController
293
295
  must_revalidate: options.delete(:must_revalidate),
294
296
  stale_while_revalidate: options.delete(:stale_while_revalidate),
295
297
  stale_if_error: options.delete(:stale_if_error),
298
+ immutable: options.delete(:immutable),
296
299
  )
297
300
  options.delete(:private)
298
301
 
@@ -316,7 +319,7 @@ module ActionController
316
319
  # user's web browser. To allow proxies to cache the response, set `true` to
317
320
  # indicate that they can serve the cached response to all users.
318
321
  def http_cache_forever(public: false)
319
- expires_in 100.years, public: public
322
+ expires_in 100.years, public: public, immutable: true
320
323
 
321
324
  yield if stale?(etag: request.fullpath,
322
325
  last_modified: Time.new(2011, 1, 1).utc,
@@ -211,7 +211,7 @@ module ActionController
211
211
  end
212
212
  end
213
213
 
214
- # Returns false on a valid response, true otherwise.
214
+ # Returns true on a valid response, false otherwise.
215
215
  def authenticate(request, realm, &password_procedure)
216
216
  request.authorization && validate_digest_response(request, realm, &password_procedure)
217
217
  end
@@ -431,7 +431,7 @@ module ActionController
431
431
  module ControllerMethods
432
432
  # Authenticate using an HTTP Bearer token, or otherwise render an HTTP header
433
433
  # requesting the client to send a Bearer token. For the authentication to be
434
- # considered successful, `login_procedure` should return a non-nil value.
434
+ # considered successful, `login_procedure` must not return a false value.
435
435
  # Typically, the authenticated user is returned.
436
436
  #
437
437
  # See ActionController::HttpAuthentication::Token for example usage.
@@ -513,11 +513,14 @@ 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
+
516
519
  # This method takes an authorization body and splits up the key-value pairs by
517
520
  # the standardized `:`, `;`, or `\t` delimiters defined in
518
521
  # `AUTHN_PAIR_DELIMITERS`.
519
522
  def raw_params(auth)
520
- _raw_params = auth.sub(TOKEN_REGEX, "").split(AUTHN_PAIR_DELIMITERS).map(&:strip)
523
+ _raw_params = auth.sub(TOKEN_REGEX, "").split(WHITESPACED_AUTHN_PAIR_DELIMITERS)
521
524
  _raw_params.reject!(&:empty?)
522
525
 
523
526
  if !_raw_params.first&.start_with?(TOKEN_KEY)
@@ -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
@@ -77,12 +77,15 @@ module ActionController
77
77
  # Writing an object will convert it into standard SSE format with whatever
78
78
  # options you have configured. You may choose to set the following options:
79
79
  #
80
- # 1) Event. If specified, an event with this name will be dispatched on
81
- # the browser.
82
- # 2) Retry. The reconnection time in milliseconds used when attempting
83
- # to send the event.
84
- # 3) Id. If the connection dies while sending an SSE to the browser, then
85
- # the server will receive a +Last-Event-ID+ header with value equal to +id+.
80
+ # `:event`
81
+ # : If specified, an event with this name will be dispatched on the browser.
82
+ #
83
+ # `:retry`
84
+ # : The reconnection time in milliseconds used when attempting to send the event.
85
+ #
86
+ # `:id`
87
+ # : If the connection dies while sending an SSE to the browser, then the
88
+ # server will receive a `Last-Event-ID` header with value equal to `id`.
86
89
  #
87
90
  # After setting an option in the constructor of the SSE object, all future SSEs
88
91
  # sent across the stream will use those options unless overridden.
@@ -304,6 +307,10 @@ module ActionController
304
307
  error = e
305
308
  end
306
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
+
307
314
  @_response.commit!
308
315
  end
309
316
  end
@@ -368,11 +375,15 @@ module ActionController
368
375
  # data from the response bodies. Nobody should call this method except in Rails
369
376
  # internals. Seriously!
370
377
  def new_controller_thread # :nodoc:
371
- Thread.new {
378
+ ActionController::Live.live_thread_pool_executor.post do
372
379
  t2 = Thread.current
373
380
  t2.abort_on_exception = true
374
381
  yield
375
- }
382
+ end
383
+ end
384
+
385
+ def self.live_thread_pool_executor
386
+ @live_thread_pool_executor ||= Concurrent::CachedThreadPool.new(name: "action_controller.live")
376
387
  end
377
388
 
378
389
  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)
@@ -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]