actionpack 7.2.1.1 → 8.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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]