actionpack 8.1.0.beta1 → 8.1.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +105 -1
- data/lib/abstract_controller/base.rb +2 -1
- data/lib/abstract_controller/helpers.rb +1 -1
- data/lib/action_controller/api.rb +1 -0
- data/lib/action_controller/base.rb +1 -0
- data/lib/action_controller/log_subscriber.rb +11 -3
- data/lib/action_controller/metal/live.rb +9 -18
- data/lib/action_controller/metal/rate_limiting.rb +9 -3
- data/lib/action_controller/metal/redirecting.rb +45 -9
- data/lib/action_controller/railtie.rb +25 -2
- data/lib/action_controller/structured_event_subscriber.rb +112 -0
- data/lib/action_dispatch/http/mime_negotiation.rb +55 -1
- data/lib/action_dispatch/http/url.rb +11 -11
- data/lib/action_dispatch/journey/gtg/transition_table.rb +9 -7
- data/lib/action_dispatch/log_subscriber.rb +7 -3
- data/lib/action_dispatch/middleware/remote_ip.rb +9 -4
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +3 -0
- data/lib/action_dispatch/railtie.rb +4 -0
- data/lib/action_dispatch/routing/inspector.rb +79 -59
- data/lib/action_dispatch/routing/mapper.rb +4 -2
- data/lib/action_dispatch/routing/redirection.rb +10 -7
- data/lib/action_dispatch/routing/routes_proxy.rb +1 -0
- data/lib/action_dispatch/structured_event_subscriber.rb +20 -0
- data/lib/action_dispatch/testing/integration.rb +3 -4
- data/lib/action_dispatch/testing/request_encoder.rb +9 -9
- data/lib/action_dispatch.rb +8 -0
- data/lib/action_pack/gem_version.rb +1 -1
- metadata +12 -10
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8a6a9e8f62251d8a2effa940639912fb8780489767c229427b614b7832cfa9db
|
|
4
|
+
data.tar.gz: 4a1143a6c62f275e05c6ed9d8f19f0361ca7a2c208045421f9e0d7e5f7e0403b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9aaa8fb203ce8b597633893fa89e55948012c7c9b8d0f8e73296fe589933dc31c047d11ff7170b905cef1e023ba5a9afb1f9c165c574bee54f90f73b5a707b31
|
|
7
|
+
data.tar.gz: db8d534af910c0bf937de64d457c24e20739c2e814b95363c9fae5726f723e7c2fc416aa94fc90754929f899095a6bdbc7f4bc9ef752010e49844cb7362d1dc7
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,108 @@
|
|
|
1
|
-
## Rails 8.1.0
|
|
1
|
+
## Rails 8.1.0 (October 22, 2025) ##
|
|
2
|
+
|
|
3
|
+
* Submit test requests using `as: :html` with `Content-Type: x-www-form-urlencoded`
|
|
4
|
+
|
|
5
|
+
*Sean Doyle*
|
|
6
|
+
|
|
7
|
+
* Add link-local IP ranges to `ActionDispatch::RemoteIp` default proxies.
|
|
8
|
+
|
|
9
|
+
Link-local addresses (`169.254.0.0/16` for IPv4 and `fe80::/10` for IPv6)
|
|
10
|
+
are now included in the default trusted proxy list, similar to private IP ranges.
|
|
11
|
+
|
|
12
|
+
*Adam Daniels*
|
|
13
|
+
|
|
14
|
+
* `remote_ip` will no longer ignore IPs in X-Forwarded-For headers if they
|
|
15
|
+
are accompanied by port information.
|
|
16
|
+
|
|
17
|
+
*Duncan Brown*, *Prevenios Marinos*, *Masafumi Koba*, *Adam Daniels*
|
|
18
|
+
|
|
19
|
+
* Add `action_dispatch.verbose_redirect_logs` setting that logs where redirects were called from.
|
|
20
|
+
|
|
21
|
+
Similar to `active_record.verbose_query_logs` and `active_job.verbose_enqueue_logs`, this adds a line in your logs that shows where a redirect was called from.
|
|
22
|
+
|
|
23
|
+
Example:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
Redirected to http://localhost:3000/posts/1
|
|
27
|
+
↳ app/controllers/posts_controller.rb:32:in `block (2 levels) in create'
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
*Dennis Paagman*
|
|
31
|
+
|
|
32
|
+
* Add engine route filtering and better formatting in `bin/rails routes`.
|
|
33
|
+
|
|
34
|
+
Allow engine routes to be filterable in the routing inspector, and
|
|
35
|
+
improve formatting of engine routing output.
|
|
36
|
+
|
|
37
|
+
Before:
|
|
38
|
+
```
|
|
39
|
+
> bin/rails routes -e engine_only
|
|
40
|
+
No routes were found for this grep pattern.
|
|
41
|
+
For more information about routes, see the Rails guide: https://guides.rubyonrails.org/routing.html.
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
After:
|
|
45
|
+
```
|
|
46
|
+
> bin/rails routes -e engine_only
|
|
47
|
+
Routes for application:
|
|
48
|
+
No routes were found for this grep pattern.
|
|
49
|
+
For more information about routes, see the Rails guide: https://guides.rubyonrails.org/routing.html.
|
|
50
|
+
|
|
51
|
+
Routes for Test::Engine:
|
|
52
|
+
Prefix Verb URI Pattern Controller#Action
|
|
53
|
+
engine GET /engine_only(.:format) a#b
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
*Dennis Paagman*, *Gannon McGibbon*
|
|
57
|
+
|
|
58
|
+
* Add structured events for Action Pack and Action Dispatch:
|
|
59
|
+
- `action_dispatch.redirect`
|
|
60
|
+
- `action_controller.request_started`
|
|
61
|
+
- `action_controller.request_completed`
|
|
62
|
+
- `action_controller.callback_halted`
|
|
63
|
+
- `action_controller.rescue_from_handled`
|
|
64
|
+
- `action_controller.file_sent`
|
|
65
|
+
- `action_controller.redirected`
|
|
66
|
+
- `action_controller.data_sent`
|
|
67
|
+
- `action_controller.unpermitted_parameters`
|
|
68
|
+
- `action_controller.fragment_cache`
|
|
69
|
+
|
|
70
|
+
*Adrianna Chang*
|
|
71
|
+
|
|
72
|
+
* URL helpers for engines mounted at the application root handle `SCRIPT_NAME` correctly.
|
|
73
|
+
|
|
74
|
+
Fixed an issue where `SCRIPT_NAME` is not applied to paths generated for routes in an engine
|
|
75
|
+
mounted at "/".
|
|
76
|
+
|
|
77
|
+
*Mike Dalessio*
|
|
78
|
+
|
|
79
|
+
* Update `ActionController::Metal::RateLimiting` to support passing method names to `:by` and `:with`
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
class SignupsController < ApplicationController
|
|
83
|
+
rate_limit to: 10, within: 1.minute, with: :redirect_with_flash
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
def redirect_with_flash
|
|
87
|
+
redirect_to root_url, alert: "Too many requests!"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
*Sean Doyle*
|
|
93
|
+
|
|
94
|
+
* Optimize `ActionDispatch::Http::URL.build_host_url` when protocol is included in host.
|
|
95
|
+
|
|
96
|
+
When using URL helpers with a host that includes the protocol (e.g., `{ host: "https://example.com" }`),
|
|
97
|
+
skip unnecessary protocol normalization and string duplication since the extracted protocol is already
|
|
98
|
+
in the correct format. This eliminates 2 string allocations per URL generation and provides a ~10%
|
|
99
|
+
performance improvement for this case.
|
|
100
|
+
|
|
101
|
+
*Joshua Young*, *Hartley McGuire*
|
|
102
|
+
|
|
103
|
+
* Allow `action_controller.logger` to be disabled by setting it to `nil` or `false` instead of always defaulting to `Rails.logger`.
|
|
104
|
+
|
|
105
|
+
*Roberto Miranda*
|
|
2
106
|
|
|
3
107
|
* Remove deprecated support to a route to multiple paths.
|
|
4
108
|
|
|
@@ -97,7 +97,8 @@ module AbstractController
|
|
|
97
97
|
methods = public_instance_methods(true) - internal_methods
|
|
98
98
|
# Be sure to include shadowed public instance methods of this class.
|
|
99
99
|
methods.concat(public_instance_methods(false))
|
|
100
|
-
methods.
|
|
100
|
+
methods.reject! { |m| m.start_with?("_") }
|
|
101
|
+
methods.map!(&:name)
|
|
101
102
|
methods.to_set
|
|
102
103
|
end
|
|
103
104
|
end
|
|
@@ -90,7 +90,7 @@ module AbstractController
|
|
|
90
90
|
#--
|
|
91
91
|
# Implemented by Resolution#modules_for_helpers.
|
|
92
92
|
|
|
93
|
-
# :method:
|
|
93
|
+
# :method: all_helpers_from_path
|
|
94
94
|
# :call-seq: all_helpers_from_path(path)
|
|
95
95
|
#
|
|
96
96
|
# Returns a list of helper names in a given path.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# :markup: markdown
|
|
4
|
-
|
|
5
3
|
module ActionController
|
|
6
|
-
class LogSubscriber < ActiveSupport::LogSubscriber
|
|
4
|
+
class LogSubscriber < ActiveSupport::LogSubscriber # :nodoc:
|
|
7
5
|
INTERNAL_PARAMS = %w(controller action format _method only_path)
|
|
8
6
|
|
|
7
|
+
class_attribute :backtrace_cleaner, default: ActiveSupport::BacktraceCleaner.new
|
|
8
|
+
|
|
9
9
|
def start_processing(event)
|
|
10
10
|
return unless logger.info?
|
|
11
11
|
|
|
@@ -63,6 +63,10 @@ module ActionController
|
|
|
63
63
|
|
|
64
64
|
def redirect_to(event)
|
|
65
65
|
info { "Redirected to #{event.payload[:location]}" }
|
|
66
|
+
|
|
67
|
+
if ActionDispatch.verbose_redirect_logs && (source = redirect_source_location)
|
|
68
|
+
info { "↳ #{source}" }
|
|
69
|
+
end
|
|
66
70
|
end
|
|
67
71
|
subscribe_log_level :redirect_to, :info
|
|
68
72
|
|
|
@@ -97,6 +101,10 @@ module ActionController
|
|
|
97
101
|
def logger
|
|
98
102
|
ActionController::Base.logger
|
|
99
103
|
end
|
|
104
|
+
|
|
105
|
+
def redirect_source_location
|
|
106
|
+
backtrace_cleaner.first_clean_frame
|
|
107
|
+
end
|
|
100
108
|
end
|
|
101
109
|
end
|
|
102
110
|
|
|
@@ -133,15 +133,16 @@ module ActionController
|
|
|
133
133
|
private
|
|
134
134
|
def perform_write(json, options)
|
|
135
135
|
current_options = @options.merge(options).stringify_keys
|
|
136
|
-
|
|
136
|
+
event = +""
|
|
137
137
|
PERMITTED_OPTIONS.each do |option_name|
|
|
138
138
|
if (option_value = current_options[option_name])
|
|
139
|
-
|
|
139
|
+
event << "#{option_name}: #{option_value}\n"
|
|
140
140
|
end
|
|
141
141
|
end
|
|
142
142
|
|
|
143
143
|
message = json.gsub("\n", "\ndata: ")
|
|
144
|
-
|
|
144
|
+
event << "data: #{message}\n\n"
|
|
145
|
+
@stream.write event
|
|
145
146
|
end
|
|
146
147
|
end
|
|
147
148
|
|
|
@@ -236,12 +237,7 @@ module ActionController
|
|
|
236
237
|
|
|
237
238
|
private
|
|
238
239
|
def each_chunk(&block)
|
|
239
|
-
|
|
240
|
-
str = nil
|
|
241
|
-
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
|
242
|
-
str = @buf.pop
|
|
243
|
-
end
|
|
244
|
-
break unless str
|
|
240
|
+
while str = @buf.pop
|
|
245
241
|
yield str
|
|
246
242
|
end
|
|
247
243
|
end
|
|
@@ -275,16 +271,14 @@ module ActionController
|
|
|
275
271
|
# This processes the action in a child thread. It lets us return the response
|
|
276
272
|
# code and headers back up the Rack stack, and still process the body in
|
|
277
273
|
# parallel with sending data to the client.
|
|
278
|
-
new_controller_thread
|
|
274
|
+
new_controller_thread do
|
|
279
275
|
ActiveSupport::Dependencies.interlock.running do
|
|
280
276
|
t2 = Thread.current
|
|
281
277
|
|
|
282
278
|
# Since we're processing the view in a different thread, copy the thread locals
|
|
283
279
|
# from the main thread to the child thread. :'(
|
|
284
280
|
locals.each { |k, v| t2[k] = v }
|
|
285
|
-
ActiveSupport::IsolatedExecutionState.share_with(t1)
|
|
286
|
-
|
|
287
|
-
begin
|
|
281
|
+
ActiveSupport::IsolatedExecutionState.share_with(t1) do
|
|
288
282
|
super(name)
|
|
289
283
|
rescue => e
|
|
290
284
|
if @_response.committed?
|
|
@@ -301,18 +295,15 @@ module ActionController
|
|
|
301
295
|
error = e
|
|
302
296
|
end
|
|
303
297
|
ensure
|
|
304
|
-
ActiveSupport::IsolatedExecutionState.clear
|
|
305
298
|
clean_up_thread_locals(locals, t2)
|
|
306
299
|
|
|
307
300
|
@_response.commit!
|
|
308
301
|
end
|
|
309
302
|
end
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
|
313
|
-
@_response.await_commit
|
|
314
303
|
end
|
|
315
304
|
|
|
305
|
+
@_response.await_commit
|
|
306
|
+
|
|
316
307
|
raise error if error
|
|
317
308
|
end
|
|
318
309
|
|
|
@@ -45,7 +45,12 @@ module ActionController # :nodoc:
|
|
|
45
45
|
#
|
|
46
46
|
# class SignupsController < ApplicationController
|
|
47
47
|
# rate_limit to: 1000, within: 10.seconds,
|
|
48
|
-
# by: -> { request.domain }, with:
|
|
48
|
+
# by: -> { request.domain }, with: :redirect_to_busy, only: :new
|
|
49
|
+
#
|
|
50
|
+
# private
|
|
51
|
+
# def redirect_to_busy
|
|
52
|
+
# redirect_to busy_controller_url, alert: "Too many signups on domain!"
|
|
53
|
+
# end
|
|
49
54
|
# end
|
|
50
55
|
#
|
|
51
56
|
# class APIController < ApplicationController
|
|
@@ -65,7 +70,8 @@ module ActionController # :nodoc:
|
|
|
65
70
|
|
|
66
71
|
private
|
|
67
72
|
def rate_limiting(to:, within:, by:, with:, store:, name:, scope:)
|
|
68
|
-
by = instance_exec(&by)
|
|
73
|
+
by = by.is_a?(Symbol) ? send(by) : instance_exec(&by)
|
|
74
|
+
|
|
69
75
|
cache_key = ["rate-limit", scope, name, by].compact.join(":")
|
|
70
76
|
count = store.increment(cache_key, 1, expires_in: within)
|
|
71
77
|
if count && count > to
|
|
@@ -78,7 +84,7 @@ module ActionController # :nodoc:
|
|
|
78
84
|
name: name,
|
|
79
85
|
scope: scope,
|
|
80
86
|
cache_key: cache_key) do
|
|
81
|
-
instance_exec(&with)
|
|
87
|
+
with.is_a?(Symbol) ? send(with) : instance_exec(&with)
|
|
82
88
|
end
|
|
83
89
|
end
|
|
84
90
|
end
|
|
@@ -11,10 +11,23 @@ module ActionController
|
|
|
11
11
|
|
|
12
12
|
class UnsafeRedirectError < StandardError; end
|
|
13
13
|
|
|
14
|
+
class OpenRedirectError < UnsafeRedirectError
|
|
15
|
+
def initialize(location)
|
|
16
|
+
super("Unsafe redirect to #{location.to_s.truncate(100).inspect}, pass allow_other_host: true to redirect anyway.")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class PathRelativeRedirectError < UnsafeRedirectError
|
|
21
|
+
def initialize(url)
|
|
22
|
+
super("Path relative URL redirect detected: #{url.inspect}")
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
14
26
|
ILLEGAL_HEADER_VALUE_REGEX = /[\x00-\x08\x0A-\x1F]/
|
|
15
27
|
|
|
16
28
|
included do
|
|
17
29
|
mattr_accessor :raise_on_open_redirects, default: false
|
|
30
|
+
mattr_accessor :action_on_open_redirect, default: :log
|
|
18
31
|
mattr_accessor :action_on_path_relative_redirect, default: :log
|
|
19
32
|
class_attribute :_allowed_redirect_hosts, :allowed_redirect_hosts_permissions, instance_accessor: false, instance_predicate: false
|
|
20
33
|
singleton_class.alias_method :allowed_redirect_hosts, :_allowed_redirect_hosts
|
|
@@ -95,16 +108,17 @@ module ActionController
|
|
|
95
108
|
# ### Open Redirect protection
|
|
96
109
|
#
|
|
97
110
|
# By default, Rails protects against redirecting to external hosts for your
|
|
98
|
-
# app's safety, so called open redirects.
|
|
99
|
-
# 7.0, after upgrading opt-in by uncommenting the line with
|
|
100
|
-
# `raise_on_open_redirects` in
|
|
101
|
-
# `config/initializers/new_framework_defaults_7_0.rb`
|
|
111
|
+
# app's safety, so called open redirects.
|
|
102
112
|
#
|
|
103
113
|
# Here #redirect_to automatically validates the potentially-unsafe URL:
|
|
104
114
|
#
|
|
105
115
|
# redirect_to params[:redirect_url]
|
|
106
116
|
#
|
|
107
|
-
#
|
|
117
|
+
# The `action_on_open_redirect` configuration option controls the behavior when an unsafe
|
|
118
|
+
# redirect is detected:
|
|
119
|
+
# * `:log` - Logs a warning but allows the redirect
|
|
120
|
+
# * `:notify` - Sends an ActiveSupport notification for monitoring
|
|
121
|
+
# * `:raise` - Raises an UnsafeRedirectError
|
|
108
122
|
#
|
|
109
123
|
# To allow any external redirects pass `allow_other_host: true`, though using a
|
|
110
124
|
# user-provided param in that case is unsafe.
|
|
@@ -137,7 +151,7 @@ module ActionController
|
|
|
137
151
|
raise ActionControllerError.new("Cannot redirect to nil!") unless options
|
|
138
152
|
raise AbstractController::DoubleRenderError if response_body
|
|
139
153
|
|
|
140
|
-
allow_other_host = response_options.delete(:allow_other_host)
|
|
154
|
+
allow_other_host = response_options.delete(:allow_other_host)
|
|
141
155
|
|
|
142
156
|
proposed_status = _extract_redirect_to_status(options, response_options)
|
|
143
157
|
|
|
@@ -244,7 +258,9 @@ module ActionController
|
|
|
244
258
|
|
|
245
259
|
private
|
|
246
260
|
def _allow_other_host
|
|
247
|
-
|
|
261
|
+
return false if raise_on_open_redirects
|
|
262
|
+
|
|
263
|
+
action_on_open_redirect != :raise
|
|
248
264
|
end
|
|
249
265
|
|
|
250
266
|
def _extract_redirect_to_status(options, response_options)
|
|
@@ -258,10 +274,30 @@ module ActionController
|
|
|
258
274
|
end
|
|
259
275
|
|
|
260
276
|
def _enforce_open_redirect_protection(location, allow_other_host:)
|
|
277
|
+
# Explictly allowed other host or host is in allow list allow redirect
|
|
261
278
|
if allow_other_host || _url_host_allowed?(location)
|
|
262
279
|
location
|
|
280
|
+
# Explicitly disallowed other host
|
|
281
|
+
elsif allow_other_host == false
|
|
282
|
+
raise OpenRedirectError.new(location)
|
|
283
|
+
# Configuration disallows other hosts
|
|
284
|
+
elsif !_allow_other_host
|
|
285
|
+
raise OpenRedirectError.new(location)
|
|
286
|
+
# Log but allow redirect
|
|
287
|
+
elsif action_on_open_redirect == :log
|
|
288
|
+
logger.warn "Open redirect to #{location.inspect} detected" if logger
|
|
289
|
+
location
|
|
290
|
+
# Notify but allow redirect
|
|
291
|
+
elsif action_on_open_redirect == :notify
|
|
292
|
+
ActiveSupport::Notifications.instrument("open_redirect.action_controller",
|
|
293
|
+
location: location,
|
|
294
|
+
request: request,
|
|
295
|
+
stack_trace: caller,
|
|
296
|
+
)
|
|
297
|
+
location
|
|
298
|
+
# Fall through, should not happen but raise for safety
|
|
263
299
|
else
|
|
264
|
-
raise
|
|
300
|
+
raise OpenRedirectError.new(location)
|
|
265
301
|
end
|
|
266
302
|
end
|
|
267
303
|
|
|
@@ -301,7 +337,7 @@ module ActionController
|
|
|
301
337
|
stack_trace: caller
|
|
302
338
|
)
|
|
303
339
|
when :raise
|
|
304
|
-
raise
|
|
340
|
+
raise PathRelativeRedirectError.new(url)
|
|
305
341
|
end
|
|
306
342
|
end
|
|
307
343
|
end
|
|
@@ -12,7 +12,7 @@ require "action_view/railtie"
|
|
|
12
12
|
module ActionController
|
|
13
13
|
class Railtie < Rails::Railtie # :nodoc:
|
|
14
14
|
config.action_controller = ActiveSupport::OrderedOptions.new
|
|
15
|
-
config.action_controller.
|
|
15
|
+
config.action_controller.action_on_open_redirect = :log
|
|
16
16
|
config.action_controller.action_on_path_relative_redirect = :log
|
|
17
17
|
config.action_controller.log_query_tags_around_actions = true
|
|
18
18
|
config.action_controller.wrap_parameters_by_default = false
|
|
@@ -57,7 +57,8 @@ module ActionController
|
|
|
57
57
|
paths = app.config.paths
|
|
58
58
|
options = app.config.action_controller
|
|
59
59
|
|
|
60
|
-
options.logger
|
|
60
|
+
options.logger = options.fetch(:logger, Rails.logger)
|
|
61
|
+
|
|
61
62
|
options.cache_store ||= Rails.cache
|
|
62
63
|
|
|
63
64
|
options.javascripts_dir ||= paths["public/javascripts"].first
|
|
@@ -103,6 +104,22 @@ module ActionController
|
|
|
103
104
|
end
|
|
104
105
|
end
|
|
105
106
|
|
|
107
|
+
initializer "action_controller.open_redirects" do |app|
|
|
108
|
+
ActiveSupport.on_load(:action_controller, run_once: true) do
|
|
109
|
+
if app.config.action_controller.has_key?(:raise_on_open_redirects)
|
|
110
|
+
ActiveSupport.deprecator.warn(<<~MSG.squish)
|
|
111
|
+
`raise_on_open_redirects` is deprecated and will be removed in a future Rails version.
|
|
112
|
+
Use `config.action_controller.action_on_open_redirect = :raise` instead.
|
|
113
|
+
MSG
|
|
114
|
+
|
|
115
|
+
# Fallback to the default behavior in case of `load_default` set `action_on_open_redirect`, but apps set `raise_on_open_redirects`.
|
|
116
|
+
if app.config.action_controller.raise_on_open_redirects == false && app.config.action_controller.action_on_open_redirect == :raise
|
|
117
|
+
self.action_on_open_redirect = :log
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
106
123
|
initializer "action_controller.query_log_tags" do |app|
|
|
107
124
|
query_logs_tags_enabled = app.config.respond_to?(:active_record) &&
|
|
108
125
|
app.config.active_record.query_log_tags_enabled &&
|
|
@@ -135,5 +152,11 @@ module ActionController
|
|
|
135
152
|
ActionController::TestCase.executor_around_each_request = app.config.active_support.executor_around_test_case
|
|
136
153
|
end
|
|
137
154
|
end
|
|
155
|
+
|
|
156
|
+
initializer "action_controller.backtrace_cleaner" do
|
|
157
|
+
ActiveSupport.on_load(:action_controller) do
|
|
158
|
+
ActionController::LogSubscriber.backtrace_cleaner = Rails.backtrace_cleaner
|
|
159
|
+
end
|
|
160
|
+
end
|
|
138
161
|
end
|
|
139
162
|
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActionController
|
|
4
|
+
class StructuredEventSubscriber < ActiveSupport::StructuredEventSubscriber # :nodoc:
|
|
5
|
+
INTERNAL_PARAMS = %w(controller action format _method only_path)
|
|
6
|
+
|
|
7
|
+
def start_processing(event)
|
|
8
|
+
payload = event.payload
|
|
9
|
+
params = {}
|
|
10
|
+
payload[:params].each_pair do |k, v|
|
|
11
|
+
params[k] = v unless INTERNAL_PARAMS.include?(k)
|
|
12
|
+
end
|
|
13
|
+
format = payload[:format]
|
|
14
|
+
format = format.to_s.upcase if format.is_a?(Symbol)
|
|
15
|
+
format = "*/*" if format.nil?
|
|
16
|
+
|
|
17
|
+
emit_event("action_controller.request_started",
|
|
18
|
+
controller: payload[:controller],
|
|
19
|
+
action: payload[:action],
|
|
20
|
+
format:,
|
|
21
|
+
params:,
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def process_action(event)
|
|
26
|
+
payload = event.payload
|
|
27
|
+
status = payload[:status]
|
|
28
|
+
|
|
29
|
+
if status.nil? && (exception_class_name = payload[:exception]&.first)
|
|
30
|
+
status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
emit_event("action_controller.request_completed", {
|
|
34
|
+
controller: payload[:controller],
|
|
35
|
+
action: payload[:action],
|
|
36
|
+
status: status,
|
|
37
|
+
**additions_for(payload),
|
|
38
|
+
duration_ms: event.duration.round(2),
|
|
39
|
+
gc_time_ms: event.gc_time.round(1),
|
|
40
|
+
}.compact)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def halted_callback(event)
|
|
44
|
+
emit_event("action_controller.callback_halted", filter: event.payload[:filter])
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def rescue_from_callback(event)
|
|
48
|
+
exception = event.payload[:exception]
|
|
49
|
+
emit_event("action_controller.rescue_from_handled",
|
|
50
|
+
exception_class: exception.class.name,
|
|
51
|
+
exception_message: exception.message,
|
|
52
|
+
exception_backtrace: exception.backtrace&.first&.delete_prefix("#{Rails.root}/")
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def send_file(event)
|
|
57
|
+
emit_event("action_controller.file_sent", path: event.payload[:path], duration_ms: event.duration.round(1))
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def redirect_to(event)
|
|
61
|
+
emit_event("action_controller.redirected", location: event.payload[:location])
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def send_data(event)
|
|
65
|
+
emit_event("action_controller.data_sent", filename: event.payload[:filename], duration_ms: event.duration.round(1))
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def unpermitted_parameters(event)
|
|
69
|
+
unpermitted_keys = event.payload[:keys]
|
|
70
|
+
context = event.payload[:context]
|
|
71
|
+
|
|
72
|
+
emit_debug_event("action_controller.unpermitted_parameters",
|
|
73
|
+
unpermitted_keys:,
|
|
74
|
+
context: context.except(:request)
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
debug_only :unpermitted_parameters
|
|
78
|
+
|
|
79
|
+
def write_fragment(event)
|
|
80
|
+
fragment_cache(__method__, event)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def read_fragment(event)
|
|
84
|
+
fragment_cache(__method__, event)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def exist_fragment?(event)
|
|
88
|
+
fragment_cache(__method__, event)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def expire_fragment(event)
|
|
92
|
+
fragment_cache(__method__, event)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
def fragment_cache(method_name, event)
|
|
97
|
+
key = ActiveSupport::Cache.expand_cache_key(event.payload[:key] || event.payload[:path])
|
|
98
|
+
|
|
99
|
+
emit_event("action_controller.fragment_cache",
|
|
100
|
+
method: "#{method_name}",
|
|
101
|
+
key: key,
|
|
102
|
+
duration_ms: event.duration.round(1)
|
|
103
|
+
)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def additions_for(payload)
|
|
107
|
+
payload.slice(:view_runtime, :db_runtime, :queries_count, :cached_queries_count)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
ActionController::StructuredEventSubscriber.attach_to :action_controller
|
|
@@ -91,7 +91,49 @@ module ActionDispatch
|
|
|
91
91
|
end
|
|
92
92
|
end
|
|
93
93
|
|
|
94
|
-
# Sets the variant for template.
|
|
94
|
+
# Sets the \variant for the response template.
|
|
95
|
+
#
|
|
96
|
+
# When determining which template to render, Action View will incorporate
|
|
97
|
+
# all variants from the request. For example, if an
|
|
98
|
+
# `ArticlesController#index` action needs to respond to
|
|
99
|
+
# `request.variant = [:ios, :turbo_native]`, it will render the
|
|
100
|
+
# first template file it can find in the following list:
|
|
101
|
+
#
|
|
102
|
+
# - `app/views/articles/index.html+ios.erb`
|
|
103
|
+
# - `app/views/articles/index.html+turbo_native.erb`
|
|
104
|
+
# - `app/views/articles/index.html.erb`
|
|
105
|
+
#
|
|
106
|
+
# Variants add context to the requests that views render appropriately.
|
|
107
|
+
# Variant names are arbitrary, and can communicate anything from the
|
|
108
|
+
# request's platform (`:android`, `:ios`, `:linux`, `:macos`, `:windows`)
|
|
109
|
+
# to its browser (`:chrome`, `:edge`, `:firefox`, `:safari`), to the type
|
|
110
|
+
# of user (`:admin`, `:guest`, `:user`).
|
|
111
|
+
#
|
|
112
|
+
# Note: Adding many new variant templates with similarities to existing
|
|
113
|
+
# template files can make maintaining your view code more difficult.
|
|
114
|
+
#
|
|
115
|
+
# #### Parameters
|
|
116
|
+
#
|
|
117
|
+
# * `variant` - a symbol name or an array of symbol names for variants
|
|
118
|
+
# used to render the response template
|
|
119
|
+
#
|
|
120
|
+
# #### Examples
|
|
121
|
+
#
|
|
122
|
+
# class ApplicationController < ActionController::Base
|
|
123
|
+
# before_action :determine_variants
|
|
124
|
+
#
|
|
125
|
+
# private
|
|
126
|
+
# def determine_variants
|
|
127
|
+
# variants = []
|
|
128
|
+
#
|
|
129
|
+
# # some code to determine the variant(s) to use
|
|
130
|
+
#
|
|
131
|
+
# variants << :ios if request.user_agent.include?("iOS")
|
|
132
|
+
# variants << :turbo_native if request.user_agent.include?("Turbo Native")
|
|
133
|
+
#
|
|
134
|
+
# request.variant = variants
|
|
135
|
+
# end
|
|
136
|
+
# end
|
|
95
137
|
def variant=(variant)
|
|
96
138
|
variant = Array(variant)
|
|
97
139
|
|
|
@@ -102,6 +144,18 @@ module ActionDispatch
|
|
|
102
144
|
end
|
|
103
145
|
end
|
|
104
146
|
|
|
147
|
+
# Returns the \variant for the response template as an instance of
|
|
148
|
+
# ActiveSupport::ArrayInquirer.
|
|
149
|
+
#
|
|
150
|
+
# request.variant = :phone
|
|
151
|
+
# request.variant.phone? # => true
|
|
152
|
+
# request.variant.tablet? # => false
|
|
153
|
+
#
|
|
154
|
+
# request.variant = [:phone, :tablet]
|
|
155
|
+
# request.variant.phone? # => true
|
|
156
|
+
# request.variant.desktop? # => false
|
|
157
|
+
# request.variant.any?(:phone, :desktop) # => true
|
|
158
|
+
# request.variant.any?(:desktop, :watch) # => false
|
|
105
159
|
def variant
|
|
106
160
|
@variant ||= ActiveSupport::ArrayInquirer.new
|
|
107
161
|
end
|