actionpack 8.0.3 → 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 +337 -158
- data/lib/abstract_controller/asset_paths.rb +4 -2
- data/lib/abstract_controller/base.rb +12 -3
- data/lib/abstract_controller/caching.rb +6 -3
- data/lib/abstract_controller/helpers.rb +1 -1
- data/lib/abstract_controller/logger.rb +2 -1
- data/lib/action_controller/api.rb +1 -0
- data/lib/action_controller/base.rb +2 -1
- data/lib/action_controller/caching.rb +1 -2
- data/lib/action_controller/form_builder.rb +1 -1
- data/lib/action_controller/log_subscriber.rb +18 -3
- data/lib/action_controller/metal/allow_browser.rb +1 -1
- data/lib/action_controller/metal/conditional_get.rb +25 -0
- data/lib/action_controller/metal/data_streaming.rb +1 -3
- data/lib/action_controller/metal/exceptions.rb +5 -0
- data/lib/action_controller/metal/flash.rb +1 -4
- data/lib/action_controller/metal/head.rb +3 -1
- data/lib/action_controller/metal/live.rb +9 -18
- data/lib/action_controller/metal/permissions_policy.rb +9 -0
- data/lib/action_controller/metal/rate_limiting.rb +30 -9
- data/lib/action_controller/metal/redirecting.rb +105 -13
- data/lib/action_controller/metal/renderers.rb +27 -6
- data/lib/action_controller/metal/rendering.rb +7 -1
- data/lib/action_controller/metal/request_forgery_protection.rb +18 -10
- data/lib/action_controller/metal/rescue.rb +9 -0
- data/lib/action_controller/railtie.rb +27 -8
- data/lib/action_controller/structured_event_subscriber.rb +112 -0
- data/lib/action_dispatch/http/cache.rb +111 -1
- data/lib/action_dispatch/http/filter_parameters.rb +5 -3
- data/lib/action_dispatch/http/mime_negotiation.rb +55 -1
- data/lib/action_dispatch/http/mime_types.rb +1 -0
- data/lib/action_dispatch/http/param_builder.rb +28 -27
- data/lib/action_dispatch/http/parameters.rb +3 -3
- data/lib/action_dispatch/http/permissions_policy.rb +4 -0
- data/lib/action_dispatch/http/query_parser.rb +12 -10
- data/lib/action_dispatch/http/request.rb +10 -5
- data/lib/action_dispatch/http/response.rb +16 -3
- data/lib/action_dispatch/http/url.rb +110 -14
- data/lib/action_dispatch/journey/gtg/simulator.rb +33 -12
- data/lib/action_dispatch/journey/gtg/transition_table.rb +33 -41
- data/lib/action_dispatch/journey/nodes/node.rb +2 -1
- data/lib/action_dispatch/journey/route.rb +45 -31
- data/lib/action_dispatch/journey/router/utils.rb +8 -14
- data/lib/action_dispatch/journey/router.rb +59 -81
- data/lib/action_dispatch/journey/routes.rb +7 -0
- data/lib/action_dispatch/journey/visitors.rb +55 -23
- data/lib/action_dispatch/journey/visualizer/fsm.js +4 -6
- data/lib/action_dispatch/log_subscriber.rb +7 -3
- data/lib/action_dispatch/middleware/cookies.rb +4 -2
- data/lib/action_dispatch/middleware/debug_exceptions.rb +7 -1
- data/lib/action_dispatch/middleware/debug_view.rb +11 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +11 -5
- data/lib/action_dispatch/middleware/executor.rb +12 -2
- data/lib/action_dispatch/middleware/public_exceptions.rb +1 -5
- data/lib/action_dispatch/middleware/remote_ip.rb +9 -4
- data/lib/action_dispatch/middleware/session/cache_store.rb +17 -0
- data/lib/action_dispatch/middleware/templates/rescues/_copy_button.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +3 -2
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +9 -5
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +4 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +50 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -0
- data/lib/action_dispatch/railtie.rb +14 -2
- data/lib/action_dispatch/routing/inspector.rb +79 -56
- data/lib/action_dispatch/routing/mapper.rb +324 -172
- data/lib/action_dispatch/routing/redirection.rb +10 -7
- data/lib/action_dispatch/routing/route_set.rb +2 -4
- data/lib/action_dispatch/structured_event_subscriber.rb +20 -0
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +2 -2
- data/lib/action_dispatch/testing/assertions/response.rb +14 -0
- data/lib/action_dispatch/testing/assertions/routing.rb +11 -3
- data/lib/action_dispatch/testing/integration.rb +1 -1
- data/lib/action_dispatch/testing/request_encoder.rb +9 -9
- data/lib/action_dispatch.rb +8 -0
- data/lib/action_pack/gem_version.rb +2 -2
- metadata +13 -10
|
@@ -27,8 +27,23 @@ module ActionController
|
|
|
27
27
|
# Default values are `:json`, `:js`, `:xml`.
|
|
28
28
|
RENDERERS = Set.new
|
|
29
29
|
|
|
30
|
+
module DeprecatedEscapeJsonResponses # :nodoc:
|
|
31
|
+
def escape_json_responses=(value)
|
|
32
|
+
if value
|
|
33
|
+
ActionController.deprecator.warn(<<~MSG.squish)
|
|
34
|
+
Setting action_controller.escape_json_responses = true is deprecated and will have no effect in Rails 8.2.
|
|
35
|
+
Set it to `false`, or remove the config.
|
|
36
|
+
MSG
|
|
37
|
+
end
|
|
38
|
+
super
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
30
42
|
included do
|
|
31
43
|
class_attribute :_renderers, default: Set.new.freeze
|
|
44
|
+
class_attribute :escape_json_responses, instance_writer: false, instance_accessor: false, default: true
|
|
45
|
+
|
|
46
|
+
singleton_class.prepend DeprecatedEscapeJsonResponses
|
|
32
47
|
end
|
|
33
48
|
|
|
34
49
|
# Used in ActionController::Base and ActionController::API to include all
|
|
@@ -86,7 +101,7 @@ module ActionController
|
|
|
86
101
|
remove_possible_method(method_name)
|
|
87
102
|
end
|
|
88
103
|
|
|
89
|
-
def self._render_with_renderer_method_name(key)
|
|
104
|
+
def self._render_with_renderer_method_name(key) # :nodoc:
|
|
90
105
|
"_render_with_renderer_#{key}"
|
|
91
106
|
end
|
|
92
107
|
|
|
@@ -140,7 +155,7 @@ module ActionController
|
|
|
140
155
|
_render_to_body_with_renderer(options) || super
|
|
141
156
|
end
|
|
142
157
|
|
|
143
|
-
def _render_to_body_with_renderer(options)
|
|
158
|
+
def _render_to_body_with_renderer(options) # :nodoc:
|
|
144
159
|
_renderers.each do |name|
|
|
145
160
|
if options.key?(name)
|
|
146
161
|
_process_options(options)
|
|
@@ -153,28 +168,34 @@ module ActionController
|
|
|
153
168
|
|
|
154
169
|
add :json do |json, options|
|
|
155
170
|
json_options = options.except(:callback, :content_type, :status)
|
|
171
|
+
json_options[:escape] ||= false if !self.class.escape_json_responses? && options[:callback].blank?
|
|
156
172
|
json = json.to_json(json_options) unless json.kind_of?(String)
|
|
157
173
|
|
|
158
174
|
if options[:callback].present?
|
|
159
175
|
if media_type.nil? || media_type == Mime[:json]
|
|
160
|
-
self.content_type =
|
|
176
|
+
self.content_type = :js
|
|
161
177
|
end
|
|
162
178
|
|
|
163
179
|
"/**/#{options[:callback]}(#{json})"
|
|
164
180
|
else
|
|
165
|
-
self.content_type =
|
|
181
|
+
self.content_type = :json if media_type.nil?
|
|
166
182
|
json
|
|
167
183
|
end
|
|
168
184
|
end
|
|
169
185
|
|
|
170
186
|
add :js do |js, options|
|
|
171
|
-
self.content_type =
|
|
187
|
+
self.content_type = :js if media_type.nil?
|
|
172
188
|
js.respond_to?(:to_js) ? js.to_js(options) : js
|
|
173
189
|
end
|
|
174
190
|
|
|
175
191
|
add :xml do |xml, options|
|
|
176
|
-
self.content_type =
|
|
192
|
+
self.content_type = :xml if media_type.nil?
|
|
177
193
|
xml.respond_to?(:to_xml) ? xml.to_xml(options) : xml
|
|
178
194
|
end
|
|
195
|
+
|
|
196
|
+
add :markdown do |md, options|
|
|
197
|
+
self.content_type = :md if media_type.nil?
|
|
198
|
+
md.respond_to?(:to_markdown) ? md.to_markdown : md
|
|
199
|
+
end
|
|
179
200
|
end
|
|
180
201
|
end
|
|
@@ -160,6 +160,12 @@ module ActionController
|
|
|
160
160
|
# render "posts/new", status: :unprocessable_entity
|
|
161
161
|
# # => renders app/views/posts/new.html.erb with HTTP status code 422
|
|
162
162
|
#
|
|
163
|
+
# `:variants`
|
|
164
|
+
# : This tells Rails to look for the first template matching any of the variations.
|
|
165
|
+
#
|
|
166
|
+
# render "posts/index", variants: [:mobile]
|
|
167
|
+
# # => renders app/views/posts/index.html+mobile.erb
|
|
168
|
+
#
|
|
163
169
|
#--
|
|
164
170
|
# Check for double render errors and set the content_type after rendering.
|
|
165
171
|
def render(*args)
|
|
@@ -208,7 +214,7 @@ module ActionController
|
|
|
208
214
|
end
|
|
209
215
|
|
|
210
216
|
def _set_html_content_type
|
|
211
|
-
self.content_type =
|
|
217
|
+
self.content_type = :html
|
|
212
218
|
end
|
|
213
219
|
|
|
214
220
|
def _set_rendered_content_type(format)
|
|
@@ -71,32 +71,39 @@ module ActionController # :nodoc:
|
|
|
71
71
|
included do
|
|
72
72
|
# Sets the token parameter name for RequestForgery. Calling
|
|
73
73
|
# `protect_from_forgery` sets it to `:authenticity_token` by default.
|
|
74
|
-
|
|
74
|
+
singleton_class.delegate :request_forgery_protection_token, :request_forgery_protection_token=, to: :config
|
|
75
|
+
delegate :request_forgery_protection_token, :request_forgery_protection_token=, to: :config
|
|
75
76
|
self.request_forgery_protection_token ||= :authenticity_token
|
|
76
77
|
|
|
77
78
|
# Holds the class which implements the request forgery protection.
|
|
78
|
-
|
|
79
|
+
singleton_class.delegate :forgery_protection_strategy, :forgery_protection_strategy=, to: :config
|
|
80
|
+
delegate :forgery_protection_strategy, :forgery_protection_strategy=, to: :config
|
|
79
81
|
self.forgery_protection_strategy = nil
|
|
80
82
|
|
|
81
83
|
# Controls whether request forgery protection is turned on or not. Turned off by
|
|
82
84
|
# default only in test mode.
|
|
83
|
-
|
|
85
|
+
singleton_class.delegate :allow_forgery_protection, :allow_forgery_protection=, to: :config
|
|
86
|
+
delegate :allow_forgery_protection, :allow_forgery_protection=, to: :config
|
|
84
87
|
self.allow_forgery_protection = true if allow_forgery_protection.nil?
|
|
85
88
|
|
|
86
89
|
# Controls whether a CSRF failure logs a warning. On by default.
|
|
87
|
-
|
|
90
|
+
singleton_class.delegate :log_warning_on_csrf_failure, :log_warning_on_csrf_failure=, to: :config
|
|
91
|
+
delegate :log_warning_on_csrf_failure, :log_warning_on_csrf_failure=, to: :config
|
|
88
92
|
self.log_warning_on_csrf_failure = true
|
|
89
93
|
|
|
90
94
|
# Controls whether the Origin header is checked in addition to the CSRF token.
|
|
91
|
-
|
|
95
|
+
singleton_class.delegate :forgery_protection_origin_check, :forgery_protection_origin_check=, to: :config
|
|
96
|
+
delegate :forgery_protection_origin_check, :forgery_protection_origin_check=, to: :config
|
|
92
97
|
self.forgery_protection_origin_check = false
|
|
93
98
|
|
|
94
99
|
# Controls whether form-action/method specific CSRF tokens are used.
|
|
95
|
-
|
|
100
|
+
singleton_class.delegate :per_form_csrf_tokens, :per_form_csrf_tokens=, to: :config
|
|
101
|
+
delegate :per_form_csrf_tokens, :per_form_csrf_tokens=, to: :config
|
|
96
102
|
self.per_form_csrf_tokens = false
|
|
97
103
|
|
|
98
104
|
# The strategy to use for storing and retrieving CSRF tokens.
|
|
99
|
-
|
|
105
|
+
singleton_class.delegate :csrf_token_storage_strategy, :csrf_token_storage_strategy=, to: :config
|
|
106
|
+
delegate :csrf_token_storage_strategy, :csrf_token_storage_strategy=, to: :config
|
|
100
107
|
self.csrf_token_storage_strategy = SessionStore.new
|
|
101
108
|
|
|
102
109
|
helper_method :form_authenticity_token
|
|
@@ -461,7 +468,7 @@ module ActionController # :nodoc:
|
|
|
461
468
|
# * Does the `X-CSRF-Token` header match the form_authenticity_token?
|
|
462
469
|
#
|
|
463
470
|
def verified_request? # :doc:
|
|
464
|
-
|
|
471
|
+
request.get? || request.head? || !protect_against_forgery? ||
|
|
465
472
|
(valid_request_origin? && any_authenticity_token_valid?)
|
|
466
473
|
end
|
|
467
474
|
|
|
@@ -621,6 +628,7 @@ module ActionController # :nodoc:
|
|
|
621
628
|
If you cannot change the referrer policy, you can disable origin checking with the
|
|
622
629
|
Rails.application.config.action_controller.forgery_protection_origin_check setting.
|
|
623
630
|
MSG
|
|
631
|
+
private_constant :NULL_ORIGIN_MESSAGE
|
|
624
632
|
|
|
625
633
|
# Checks if the request originated from the same origin by looking at the Origin
|
|
626
634
|
# header.
|
|
@@ -634,7 +642,7 @@ module ActionController # :nodoc:
|
|
|
634
642
|
end
|
|
635
643
|
end
|
|
636
644
|
|
|
637
|
-
def normalize_action_path(action_path)
|
|
645
|
+
def normalize_action_path(action_path)
|
|
638
646
|
uri = URI.parse(action_path)
|
|
639
647
|
|
|
640
648
|
if uri.relative? && (action_path.blank? || !action_path.start_with?("/"))
|
|
@@ -644,7 +652,7 @@ module ActionController # :nodoc:
|
|
|
644
652
|
end
|
|
645
653
|
end
|
|
646
654
|
|
|
647
|
-
def normalize_relative_action_path(rel_action_path)
|
|
655
|
+
def normalize_relative_action_path(rel_action_path)
|
|
648
656
|
uri = URI.parse(request.path)
|
|
649
657
|
# add the action path to the request.path
|
|
650
658
|
uri.path += "/#{rel_action_path}"
|
|
@@ -13,6 +13,15 @@ module ActionController # :nodoc:
|
|
|
13
13
|
extend ActiveSupport::Concern
|
|
14
14
|
include ActiveSupport::Rescuable
|
|
15
15
|
|
|
16
|
+
module ClassMethods
|
|
17
|
+
def handler_for_rescue(exception, ...) # :nodoc:
|
|
18
|
+
if handler = super
|
|
19
|
+
ActiveSupport::Notifications.instrument("rescue_from_callback.action_controller", exception: exception)
|
|
20
|
+
handler
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
16
25
|
# Override this method if you want to customize when detailed exceptions must be
|
|
17
26
|
# shown. This method is only called when `consider_all_requests_local` is
|
|
18
27
|
# `false`. By default, it returns `false`, but someone may set it to
|
|
@@ -12,9 +12,11 @@ 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
|
+
config.action_controller.action_on_path_relative_redirect = :log
|
|
16
17
|
config.action_controller.log_query_tags_around_actions = true
|
|
17
18
|
config.action_controller.wrap_parameters_by_default = false
|
|
19
|
+
config.action_controller.allowed_redirect_hosts = []
|
|
18
20
|
|
|
19
21
|
config.eager_load_namespaces << AbstractController
|
|
20
22
|
config.eager_load_namespaces << ActionController
|
|
@@ -55,7 +57,8 @@ module ActionController
|
|
|
55
57
|
paths = app.config.paths
|
|
56
58
|
options = app.config.action_controller
|
|
57
59
|
|
|
58
|
-
options.logger
|
|
60
|
+
options.logger = options.fetch(:logger, Rails.logger)
|
|
61
|
+
|
|
59
62
|
options.cache_store ||= Rails.cache
|
|
60
63
|
|
|
61
64
|
options.javascripts_dir ||= paths["public/javascripts"].first
|
|
@@ -93,12 +96,6 @@ module ActionController
|
|
|
93
96
|
end
|
|
94
97
|
end
|
|
95
98
|
|
|
96
|
-
initializer "action_controller.compile_config_methods" do
|
|
97
|
-
ActiveSupport.on_load(:action_controller) do
|
|
98
|
-
config.compile_methods! if config.respond_to?(:compile_methods!)
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
|
|
102
99
|
initializer "action_controller.request_forgery_protection" do |app|
|
|
103
100
|
ActiveSupport.on_load(:action_controller_base) do
|
|
104
101
|
if app.config.action_controller.default_protect_from_forgery
|
|
@@ -107,6 +104,22 @@ module ActionController
|
|
|
107
104
|
end
|
|
108
105
|
end
|
|
109
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
|
+
|
|
110
123
|
initializer "action_controller.query_log_tags" do |app|
|
|
111
124
|
query_logs_tags_enabled = app.config.respond_to?(:active_record) &&
|
|
112
125
|
app.config.active_record.query_log_tags_enabled &&
|
|
@@ -139,5 +152,11 @@ module ActionController
|
|
|
139
152
|
ActionController::TestCase.executor_around_each_request = app.config.active_support.executor_around_test_case
|
|
140
153
|
end
|
|
141
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
|
|
142
161
|
end
|
|
143
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
|
|
@@ -63,6 +63,114 @@ module ActionDispatch
|
|
|
63
63
|
success
|
|
64
64
|
end
|
|
65
65
|
end
|
|
66
|
+
|
|
67
|
+
def cache_control_directives
|
|
68
|
+
@cache_control_directives ||= CacheControlDirectives.new(get_header("HTTP_CACHE_CONTROL"))
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Represents the HTTP Cache-Control header for requests,
|
|
72
|
+
# providing methods to access various cache control directives
|
|
73
|
+
# Reference: https://www.rfc-editor.org/rfc/rfc9111.html#name-request-directives
|
|
74
|
+
class CacheControlDirectives
|
|
75
|
+
def initialize(cache_control_header)
|
|
76
|
+
@only_if_cached = false
|
|
77
|
+
@no_cache = false
|
|
78
|
+
@no_store = false
|
|
79
|
+
@no_transform = false
|
|
80
|
+
@max_age = nil
|
|
81
|
+
@max_stale = nil
|
|
82
|
+
@min_fresh = nil
|
|
83
|
+
@stale_if_error = false
|
|
84
|
+
parse_directives(cache_control_header)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Returns true if the only-if-cached directive is present.
|
|
88
|
+
# This directive indicates that the client only wishes to obtain a
|
|
89
|
+
# stored response. If a valid stored response is not available,
|
|
90
|
+
# the server should respond with a 504 (Gateway Timeout) status.
|
|
91
|
+
def only_if_cached?
|
|
92
|
+
@only_if_cached
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Returns true if the no-cache directive is present.
|
|
96
|
+
# This directive indicates that a cache must not use the response
|
|
97
|
+
# to satisfy subsequent requests without successful validation on the origin server.
|
|
98
|
+
def no_cache?
|
|
99
|
+
@no_cache
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Returns true if the no-store directive is present.
|
|
103
|
+
# This directive indicates that a cache must not store any part of the
|
|
104
|
+
# request or response.
|
|
105
|
+
def no_store?
|
|
106
|
+
@no_store
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Returns true if the no-transform directive is present.
|
|
110
|
+
# This directive indicates that a cache or proxy must not transform the payload.
|
|
111
|
+
def no_transform?
|
|
112
|
+
@no_transform
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Returns the value of the max-age directive.
|
|
116
|
+
# This directive indicates that the client is willing to accept a response
|
|
117
|
+
# whose age is no greater than the specified number of seconds.
|
|
118
|
+
attr_reader :max_age
|
|
119
|
+
|
|
120
|
+
# Returns the value of the max-stale directive.
|
|
121
|
+
# When max-stale is present with a value, returns that integer value.
|
|
122
|
+
# When max-stale is present without a value, returns true (unlimited staleness).
|
|
123
|
+
# When max-stale is not present, returns nil.
|
|
124
|
+
attr_reader :max_stale
|
|
125
|
+
|
|
126
|
+
# Returns true if max-stale directive is present (with or without a value)
|
|
127
|
+
def max_stale?
|
|
128
|
+
!@max_stale.nil?
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Returns true if max-stale directive is present without a value (unlimited staleness)
|
|
132
|
+
def max_stale_unlimited?
|
|
133
|
+
@max_stale == true
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Returns the value of the min-fresh directive.
|
|
137
|
+
# This directive indicates that the client is willing to accept a response
|
|
138
|
+
# whose freshness lifetime is no less than its current age plus the specified time in seconds.
|
|
139
|
+
attr_reader :min_fresh
|
|
140
|
+
|
|
141
|
+
# Returns the value of the stale-if-error directive.
|
|
142
|
+
# This directive indicates that the client is willing to accept a stale response
|
|
143
|
+
# if the check for a fresh one fails with an error for the specified number of seconds.
|
|
144
|
+
attr_reader :stale_if_error
|
|
145
|
+
|
|
146
|
+
private
|
|
147
|
+
def parse_directives(header_value)
|
|
148
|
+
return unless header_value
|
|
149
|
+
|
|
150
|
+
header_value.delete(" ").downcase.split(",").each do |directive|
|
|
151
|
+
name, value = directive.split("=", 2)
|
|
152
|
+
|
|
153
|
+
case name
|
|
154
|
+
when "max-age"
|
|
155
|
+
@max_age = value.to_i
|
|
156
|
+
when "min-fresh"
|
|
157
|
+
@min_fresh = value.to_i
|
|
158
|
+
when "stale-if-error"
|
|
159
|
+
@stale_if_error = value.to_i
|
|
160
|
+
when "no-cache"
|
|
161
|
+
@no_cache = true
|
|
162
|
+
when "no-store"
|
|
163
|
+
@no_store = true
|
|
164
|
+
when "no-transform"
|
|
165
|
+
@no_transform = true
|
|
166
|
+
when "only-if-cached"
|
|
167
|
+
@only_if_cached = true
|
|
168
|
+
when "max-stale"
|
|
169
|
+
@max_stale = value ? value.to_i : true
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
66
174
|
end
|
|
67
175
|
|
|
68
176
|
module Response
|
|
@@ -142,7 +250,7 @@ module ActionDispatch
|
|
|
142
250
|
private
|
|
143
251
|
DATE = "Date"
|
|
144
252
|
LAST_MODIFIED = "Last-Modified"
|
|
145
|
-
SPECIAL_KEYS = Set.new(%w[extras no-store no-cache max-age public private must-revalidate])
|
|
253
|
+
SPECIAL_KEYS = Set.new(%w[extras no-store no-cache max-age public private must-revalidate must-understand])
|
|
146
254
|
|
|
147
255
|
def generate_weak_etag(validators)
|
|
148
256
|
"W/#{generate_strong_etag(validators)}"
|
|
@@ -187,6 +295,7 @@ module ActionDispatch
|
|
|
187
295
|
PRIVATE = "private"
|
|
188
296
|
MUST_REVALIDATE = "must-revalidate"
|
|
189
297
|
IMMUTABLE = "immutable"
|
|
298
|
+
MUST_UNDERSTAND = "must-understand"
|
|
190
299
|
|
|
191
300
|
def handle_conditional_get!
|
|
192
301
|
# Normally default cache control setting is handled by ETag middleware. But, if
|
|
@@ -221,6 +330,7 @@ module ActionDispatch
|
|
|
221
330
|
|
|
222
331
|
if control[:no_store]
|
|
223
332
|
options << PRIVATE if control[:private]
|
|
333
|
+
options << MUST_UNDERSTAND if control[:must_understand]
|
|
224
334
|
options << NO_STORE
|
|
225
335
|
elsif control[:no_cache]
|
|
226
336
|
options << PUBLIC if control[:public]
|
|
@@ -17,9 +17,11 @@ module ActionDispatch
|
|
|
17
17
|
# For more information about filter behavior, see
|
|
18
18
|
# ActiveSupport::ParameterFilter.
|
|
19
19
|
module FilterParameters
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
# :stopdoc:
|
|
21
|
+
ENV_MATCH = [/RAW_POST_DATA/, "rack.request.form_vars"]
|
|
22
|
+
NULL_PARAM_FILTER = ActiveSupport::ParameterFilter.new
|
|
23
|
+
NULL_ENV_FILTER = ActiveSupport::ParameterFilter.new ENV_MATCH
|
|
24
|
+
# :startdoc:
|
|
23
25
|
|
|
24
26
|
def initialize
|
|
25
27
|
super
|
|
@@ -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
|
|
@@ -13,6 +13,7 @@ Mime::Type.register "text/calendar", :ics
|
|
|
13
13
|
Mime::Type.register "text/csv", :csv
|
|
14
14
|
Mime::Type.register "text/vcard", :vcf
|
|
15
15
|
Mime::Type.register "text/vtt", :vtt, %w(vtt)
|
|
16
|
+
Mime::Type.register "text/markdown", :md, [], %w(md markdown)
|
|
16
17
|
|
|
17
18
|
Mime::Type.register "image/png", :png, [], %w(png)
|
|
18
19
|
Mime::Type.register "image/jpeg", :jpeg, [], %w(jpg jpeg jpe pjpeg)
|