actionpack 6.1.7.5 → 7.1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +355 -435
- data/MIT-LICENSE +2 -1
- data/README.rdoc +6 -7
- data/lib/abstract_controller/asset_paths.rb +1 -1
- data/lib/abstract_controller/base.rb +33 -37
- data/lib/abstract_controller/caching/fragments.rb +4 -2
- data/lib/abstract_controller/caching.rb +1 -1
- data/lib/abstract_controller/callbacks.rb +50 -11
- data/lib/abstract_controller/collector.rb +2 -2
- data/lib/abstract_controller/deprecator.rb +7 -0
- data/lib/abstract_controller/error.rb +1 -1
- data/lib/abstract_controller/helpers.rb +78 -30
- data/lib/abstract_controller/logger.rb +1 -1
- data/lib/abstract_controller/railties/routes_helpers.rb +3 -16
- data/lib/abstract_controller/rendering.rb +12 -14
- data/lib/abstract_controller/translation.rb +26 -7
- data/lib/abstract_controller/url_for.rb +6 -6
- data/lib/abstract_controller.rb +6 -0
- data/lib/action_controller/api.rb +12 -10
- data/lib/action_controller/base.rb +8 -21
- data/lib/action_controller/caching.rb +2 -0
- data/lib/action_controller/deprecator.rb +7 -0
- data/lib/action_controller/form_builder.rb +4 -2
- data/lib/action_controller/log_subscriber.rb +20 -7
- data/lib/action_controller/metal/basic_implicit_render.rb +3 -1
- data/lib/action_controller/metal/conditional_get.rb +137 -102
- data/lib/action_controller/metal/content_security_policy.rb +37 -3
- data/lib/action_controller/metal/cookies.rb +1 -1
- data/lib/action_controller/metal/data_streaming.rb +25 -31
- data/lib/action_controller/metal/default_headers.rb +2 -0
- data/lib/action_controller/metal/etag_with_flash.rb +3 -1
- data/lib/action_controller/metal/etag_with_template_digest.rb +2 -0
- data/lib/action_controller/metal/exceptions.rb +27 -30
- data/lib/action_controller/metal/flash.rb +6 -2
- data/lib/action_controller/metal/head.rb +9 -7
- data/lib/action_controller/metal/helpers.rb +5 -16
- data/lib/action_controller/metal/http_authentication.rb +78 -42
- data/lib/action_controller/metal/implicit_render.rb +5 -3
- data/lib/action_controller/metal/instrumentation.rb +62 -50
- data/lib/action_controller/metal/live.rb +67 -2
- data/lib/action_controller/metal/mime_responds.rb +5 -5
- data/lib/action_controller/metal/params_wrapper.rb +24 -13
- data/lib/action_controller/metal/permissions_policy.rb +20 -29
- data/lib/action_controller/metal/redirecting.rb +96 -23
- data/lib/action_controller/metal/renderers.rb +14 -15
- data/lib/action_controller/metal/rendering.rb +121 -16
- data/lib/action_controller/metal/request_forgery_protection.rb +208 -68
- data/lib/action_controller/metal/rescue.rb +7 -4
- data/lib/action_controller/metal/streaming.rb +74 -36
- data/lib/action_controller/metal/strong_parameters.rb +254 -151
- data/lib/action_controller/metal/testing.rb +9 -2
- data/lib/action_controller/metal/url_for.rb +10 -5
- data/lib/action_controller/metal.rb +89 -34
- data/lib/action_controller/railtie.rb +66 -9
- data/lib/action_controller/renderer.rb +99 -85
- data/lib/action_controller/test_case.rb +42 -11
- data/lib/action_controller.rb +10 -6
- data/lib/action_dispatch/constants.rb +32 -0
- data/lib/action_dispatch/deprecator.rb +7 -0
- data/lib/action_dispatch/http/cache.rb +21 -16
- data/lib/action_dispatch/http/content_security_policy.rb +122 -44
- data/lib/action_dispatch/http/filter_parameters.rb +14 -23
- data/lib/action_dispatch/http/headers.rb +3 -1
- data/lib/action_dispatch/http/mime_negotiation.rb +25 -15
- data/lib/action_dispatch/http/mime_type.rb +43 -22
- data/lib/action_dispatch/http/mime_types.rb +3 -1
- data/lib/action_dispatch/http/parameters.rb +6 -6
- data/lib/action_dispatch/http/permissions_policy.rb +57 -19
- data/lib/action_dispatch/http/rack_cache.rb +2 -0
- data/lib/action_dispatch/http/request.rb +75 -51
- data/lib/action_dispatch/http/response.rb +81 -77
- data/lib/action_dispatch/http/upload.rb +15 -2
- data/lib/action_dispatch/http/url.rb +11 -19
- data/lib/action_dispatch/journey/formatter.rb +8 -2
- data/lib/action_dispatch/journey/gtg/builder.rb +11 -12
- data/lib/action_dispatch/journey/gtg/simulator.rb +10 -4
- data/lib/action_dispatch/journey/gtg/transition_table.rb +77 -21
- data/lib/action_dispatch/journey/nodes/node.rb +70 -5
- data/lib/action_dispatch/journey/path/pattern.rb +36 -27
- data/lib/action_dispatch/journey/route.rb +8 -14
- data/lib/action_dispatch/journey/router/utils.rb +2 -2
- data/lib/action_dispatch/journey/router.rb +10 -9
- data/lib/action_dispatch/journey/routes.rb +5 -5
- data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
- data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
- data/lib/action_dispatch/log_subscriber.rb +23 -0
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +5 -7
- data/lib/action_dispatch/middleware/assume_ssl.rb +24 -0
- data/lib/action_dispatch/middleware/callbacks.rb +2 -0
- data/lib/action_dispatch/middleware/cookies.rb +97 -107
- data/lib/action_dispatch/middleware/debug_exceptions.rb +31 -28
- data/lib/action_dispatch/middleware/debug_locks.rb +7 -4
- data/lib/action_dispatch/middleware/debug_view.rb +7 -2
- data/lib/action_dispatch/middleware/exception_wrapper.rb +190 -27
- data/lib/action_dispatch/middleware/executor.rb +3 -0
- data/lib/action_dispatch/middleware/flash.rb +24 -18
- data/lib/action_dispatch/middleware/host_authorization.rb +19 -20
- data/lib/action_dispatch/middleware/public_exceptions.rb +5 -3
- data/lib/action_dispatch/middleware/reloader.rb +7 -5
- data/lib/action_dispatch/middleware/remote_ip.rb +32 -19
- data/lib/action_dispatch/middleware/request_id.rb +5 -3
- data/lib/action_dispatch/middleware/server_timing.rb +76 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +6 -1
- data/lib/action_dispatch/middleware/session/cache_store.rb +2 -0
- data/lib/action_dispatch/middleware/session/cookie_store.rb +19 -13
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +3 -1
- data/lib/action_dispatch/middleware/show_exceptions.rb +30 -25
- data/lib/action_dispatch/middleware/ssl.rb +18 -6
- data/lib/action_dispatch/middleware/stack.rb +34 -11
- data/lib/action_dispatch/middleware/static.rb +16 -16
- data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +5 -5
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +4 -11
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +10 -5
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +7 -3
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +9 -9
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +45 -18
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -15
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +6 -6
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +7 -7
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +3 -0
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +64 -55
- data/lib/action_dispatch/railtie.rb +20 -4
- data/lib/action_dispatch/request/session.rb +59 -19
- data/lib/action_dispatch/request/utils.rb +8 -3
- data/lib/action_dispatch/routing/inspector.rb +55 -7
- data/lib/action_dispatch/routing/mapper.rb +117 -107
- data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -0
- data/lib/action_dispatch/routing/redirection.rb +20 -8
- data/lib/action_dispatch/routing/route_set.rb +67 -27
- data/lib/action_dispatch/routing/routes_proxy.rb +11 -16
- data/lib/action_dispatch/routing/url_for.rb +29 -26
- data/lib/action_dispatch/routing.rb +12 -13
- data/lib/action_dispatch/system_test_case.rb +8 -8
- data/lib/action_dispatch/system_testing/browser.rb +20 -29
- data/lib/action_dispatch/system_testing/driver.rb +34 -18
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +35 -20
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +0 -8
- data/lib/action_dispatch/testing/assertion_response.rb +1 -1
- data/lib/action_dispatch/testing/assertions/response.rb +14 -7
- data/lib/action_dispatch/testing/assertions/routing.rb +70 -30
- data/lib/action_dispatch/testing/assertions.rb +3 -4
- data/lib/action_dispatch/testing/integration.rb +33 -25
- data/lib/action_dispatch/testing/request_encoder.rb +4 -1
- data/lib/action_dispatch/testing/test_process.rb +5 -30
- data/lib/action_dispatch/testing/test_request.rb +1 -1
- data/lib/action_dispatch/testing/test_response.rb +34 -2
- data/lib/action_dispatch.rb +38 -4
- data/lib/action_pack/gem_version.rb +4 -4
- data/lib/action_pack/version.rb +1 -1
- data/lib/action_pack.rb +1 -1
- metadata +67 -30
@@ -4,6 +4,8 @@ require "benchmark"
|
|
4
4
|
require "abstract_controller/logger"
|
5
5
|
|
6
6
|
module ActionController
|
7
|
+
# = Action Controller \Instrumentation
|
8
|
+
#
|
7
9
|
# Adds instrumentation to several ends in ActionController::Base. It also provides
|
8
10
|
# some hooks related with process_action. This allows an ORM like Active Record
|
9
11
|
# and/or DataMapper to plug in ActionController and show related information.
|
@@ -16,28 +18,9 @@ module ActionController
|
|
16
18
|
|
17
19
|
attr_internal :view_runtime
|
18
20
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
22
|
-
action: action_name,
|
23
|
-
request: request,
|
24
|
-
params: request.filtered_parameters,
|
25
|
-
headers: request.headers,
|
26
|
-
format: request.format.ref,
|
27
|
-
method: request.request_method,
|
28
|
-
path: request.fullpath
|
29
|
-
}
|
30
|
-
|
31
|
-
ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload)
|
32
|
-
|
33
|
-
ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
|
34
|
-
result = super
|
35
|
-
payload[:response] = response
|
36
|
-
payload[:status] = response.status
|
37
|
-
result
|
38
|
-
ensure
|
39
|
-
append_info_to_payload(payload)
|
40
|
-
end
|
21
|
+
def initialize(...) # :nodoc:
|
22
|
+
super
|
23
|
+
self.view_runtime = nil
|
41
24
|
end
|
42
25
|
|
43
26
|
def render(*)
|
@@ -70,37 +53,66 @@ module ActionController
|
|
70
53
|
end
|
71
54
|
end
|
72
55
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
ActiveSupport::Notifications.instrument("halted_callback.action_controller", filter: filter)
|
77
|
-
end
|
56
|
+
private
|
57
|
+
def process_action(*)
|
58
|
+
ActiveSupport::ExecutionContext[:controller] = self
|
78
59
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
60
|
+
raw_payload = {
|
61
|
+
controller: self.class.name,
|
62
|
+
action: action_name,
|
63
|
+
request: request,
|
64
|
+
params: request.filtered_parameters,
|
65
|
+
headers: request.headers,
|
66
|
+
format: request.format.ref,
|
67
|
+
method: request.request_method,
|
68
|
+
path: request.filtered_path
|
69
|
+
}
|
88
70
|
|
89
|
-
|
90
|
-
# with the payload, so you can add more information.
|
91
|
-
def append_info_to_payload(payload) # :doc:
|
92
|
-
payload[:view_runtime] = view_runtime
|
93
|
-
end
|
71
|
+
ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload)
|
94
72
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
73
|
+
ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
|
74
|
+
result = super
|
75
|
+
payload[:response] = response
|
76
|
+
payload[:status] = response.status
|
77
|
+
result
|
78
|
+
rescue => error
|
79
|
+
payload[:status] = ActionDispatch::ExceptionWrapper.status_code_for_exception(error.class.name)
|
80
|
+
raise
|
81
|
+
ensure
|
82
|
+
append_info_to_payload(payload)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# A hook invoked every time a before callback is halted.
|
87
|
+
def halted_callback_hook(filter, _)
|
88
|
+
ActiveSupport::Notifications.instrument("halted_callback.action_controller", filter: filter)
|
89
|
+
end
|
90
|
+
|
91
|
+
# A hook which allows you to clean up any time, wrongly taken into account in
|
92
|
+
# views, like database querying time.
|
93
|
+
#
|
94
|
+
# def cleanup_view_runtime
|
95
|
+
# super - time_taken_in_something_expensive
|
96
|
+
# end
|
97
|
+
def cleanup_view_runtime # :doc:
|
98
|
+
yield
|
99
|
+
end
|
100
|
+
|
101
|
+
# Every time after an action is processed, this method is invoked
|
102
|
+
# with the payload, so you can add more information.
|
103
|
+
def append_info_to_payload(payload) # :doc:
|
104
|
+
payload[:view_runtime] = view_runtime
|
105
|
+
end
|
106
|
+
|
107
|
+
module ClassMethods
|
108
|
+
# A hook which allows other frameworks to log what happened during
|
109
|
+
# controller process action. This method should return an array
|
110
|
+
# with the messages to be added.
|
111
|
+
def log_process_action(payload) # :nodoc:
|
112
|
+
messages, view_runtime = [], payload[:view_runtime]
|
113
|
+
messages << ("Views: %.1fms" % view_runtime.to_f) if view_runtime
|
114
|
+
messages
|
115
|
+
end
|
103
116
|
end
|
104
|
-
end
|
105
117
|
end
|
106
118
|
end
|
@@ -5,6 +5,8 @@ require "delegate"
|
|
5
5
|
require "active_support/json"
|
6
6
|
|
7
7
|
module ActionController
|
8
|
+
# = Action Controller \Live
|
9
|
+
#
|
8
10
|
# Mix this module into your controller, and all actions in that controller
|
9
11
|
# will be able to stream data to the client as it's written.
|
10
12
|
#
|
@@ -34,6 +36,21 @@ module ActionController
|
|
34
36
|
# The final caveat is that your actions are executed in a separate thread than
|
35
37
|
# the main thread. Make sure your actions are thread safe, and this shouldn't
|
36
38
|
# be a problem (don't share state across threads, etc).
|
39
|
+
#
|
40
|
+
# Note that \Rails includes +Rack::ETag+ by default, which will buffer your
|
41
|
+
# response. As a result, streaming responses may not work properly with Rack
|
42
|
+
# 2.2.x, and you may need to implement workarounds in your application.
|
43
|
+
# You can either set the +ETag+ or +Last-Modified+ response headers or remove
|
44
|
+
# +Rack::ETag+ from the middleware stack to address this issue.
|
45
|
+
#
|
46
|
+
# Here's an example of how you can set the +Last-Modified+ header if your Rack
|
47
|
+
# version is 2.2.x:
|
48
|
+
#
|
49
|
+
# def stream
|
50
|
+
# response.headers["Content-Type"] = "text/event-stream"
|
51
|
+
# response.headers["Last-Modified"] = Time.now.httpdate # Add this line if your Rack version is 2.2.x
|
52
|
+
# ...
|
53
|
+
# end
|
37
54
|
module Live
|
38
55
|
extend ActiveSupport::Concern
|
39
56
|
|
@@ -49,6 +66,8 @@ module ActionController
|
|
49
66
|
end
|
50
67
|
end
|
51
68
|
|
69
|
+
# = Action Controller \Live Server Sent Events
|
70
|
+
#
|
52
71
|
# This class provides the ability to write an SSE (Server Sent Event)
|
53
72
|
# to an IO stream. The class is initialized with a stream and can be used
|
54
73
|
# to either write a JSON string or an object which can be converted to JSON.
|
@@ -124,7 +143,7 @@ module ActionController
|
|
124
143
|
class ClientDisconnected < RuntimeError
|
125
144
|
end
|
126
145
|
|
127
|
-
class Buffer < ActionDispatch::Response::Buffer
|
146
|
+
class Buffer < ActionDispatch::Response::Buffer # :nodoc:
|
128
147
|
include MonitorMixin
|
129
148
|
|
130
149
|
class << self
|
@@ -148,6 +167,11 @@ module ActionController
|
|
148
167
|
@ignore_disconnect = false
|
149
168
|
end
|
150
169
|
|
170
|
+
# ActionDispatch::Response delegates #to_ary to the internal ActionDispatch::Response::Buffer,
|
171
|
+
# defining #to_ary is an indicator that the response body can be buffered and/or cached by
|
172
|
+
# Rack middlewares, this is not the case for Live responses so we undefine it for this Buffer subclass.
|
173
|
+
undef_method :to_ary
|
174
|
+
|
151
175
|
def write(string)
|
152
176
|
unless @response.committed?
|
153
177
|
@response.headers["Cache-Control"] ||= "no-cache"
|
@@ -168,6 +192,11 @@ module ActionController
|
|
168
192
|
end
|
169
193
|
end
|
170
194
|
|
195
|
+
# Same as +write+ but automatically include a newline at the end of the string.
|
196
|
+
def writeln(string)
|
197
|
+
write string.end_with?("\n") ? string : "#{string}\n"
|
198
|
+
end
|
199
|
+
|
171
200
|
# Write a 'close' event to the buffer; the producer/writing thread
|
172
201
|
# uses this to notify us that it's finished supplying content.
|
173
202
|
#
|
@@ -225,7 +254,7 @@ module ActionController
|
|
225
254
|
end
|
226
255
|
end
|
227
256
|
|
228
|
-
class Response < ActionDispatch::Response
|
257
|
+
class Response < ActionDispatch::Response # :nodoc: all
|
229
258
|
private
|
230
259
|
def before_committed
|
231
260
|
super
|
@@ -256,6 +285,7 @@ module ActionController
|
|
256
285
|
# Since we're processing the view in a different thread, copy the
|
257
286
|
# thread locals from the main thread to the child thread. :'(
|
258
287
|
locals.each { |k, v| t2[k] = v }
|
288
|
+
ActiveSupport::IsolatedExecutionState.share_with(t1)
|
259
289
|
|
260
290
|
begin
|
261
291
|
super(name)
|
@@ -291,6 +321,41 @@ module ActionController
|
|
291
321
|
response.close if response
|
292
322
|
end
|
293
323
|
|
324
|
+
# Sends a stream to the browser, which is helpful when you're generating exports or other running data where you
|
325
|
+
# don't want the entire file buffered in memory first. Similar to send_data, but where the data is generated live.
|
326
|
+
#
|
327
|
+
# Options:
|
328
|
+
# * <tt>:filename</tt> - suggests a filename for the browser to use.
|
329
|
+
# * <tt>:type</tt> - specifies an HTTP content type.
|
330
|
+
# You can specify either a string or a symbol for a registered type with <tt>Mime::Type.register</tt>, for example :json.
|
331
|
+
# If omitted, type will be inferred from the file extension specified in <tt>:filename</tt>.
|
332
|
+
# If no content type is registered for the extension, the default type 'application/octet-stream' will be used.
|
333
|
+
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
|
334
|
+
# Valid values are 'inline' and 'attachment' (default).
|
335
|
+
#
|
336
|
+
# Example of generating a csv export:
|
337
|
+
#
|
338
|
+
# send_stream(filename: "subscribers.csv") do |stream|
|
339
|
+
# stream.write "email_address,updated_at\n"
|
340
|
+
#
|
341
|
+
# @subscribers.find_each do |subscriber|
|
342
|
+
# stream.write "#{subscriber.email_address},#{subscriber.updated_at}\n"
|
343
|
+
# end
|
344
|
+
# end
|
345
|
+
def send_stream(filename:, disposition: "attachment", type: nil)
|
346
|
+
response.headers["Content-Type"] =
|
347
|
+
(type.is_a?(Symbol) ? Mime[type].to_s : type) ||
|
348
|
+
Mime::Type.lookup_by_extension(File.extname(filename).downcase.delete("."))&.to_s ||
|
349
|
+
"application/octet-stream"
|
350
|
+
|
351
|
+
response.headers["Content-Disposition"] =
|
352
|
+
ActionDispatch::Http::ContentDisposition.format(disposition: disposition, filename: filename)
|
353
|
+
|
354
|
+
yield response.stream
|
355
|
+
ensure
|
356
|
+
response.stream.close
|
357
|
+
end
|
358
|
+
|
294
359
|
private
|
295
360
|
# Spawn a new thread to serve up the controller in. This is to get
|
296
361
|
# around the fact that Rack isn't based around IOs and we need to use
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require "abstract_controller/collector"
|
4
4
|
|
5
|
-
module ActionController
|
5
|
+
module ActionController # :nodoc:
|
6
6
|
module MimeResponds
|
7
7
|
# Without web-service support, an action which collects the data for displaying a list of people
|
8
8
|
# might look something like this:
|
@@ -32,7 +32,7 @@ module ActionController #:nodoc:
|
|
32
32
|
#
|
33
33
|
# What that says is, "if the client wants HTML or JS in response to this action, just respond as we
|
34
34
|
# would have before, but if the client wants XML, return them the list of people in XML format."
|
35
|
-
# (Rails determines the desired response format from the HTTP Accept header submitted by the client.)
|
35
|
+
# (\Rails determines the desired response format from the HTTP Accept header submitted by the client.)
|
36
36
|
#
|
37
37
|
# Supposing you have an action that adds a new person, optionally creating their company
|
38
38
|
# (by name) if it does not already exist, without web-services, it might look like this:
|
@@ -98,12 +98,12 @@ module ActionController #:nodoc:
|
|
98
98
|
#
|
99
99
|
# Note that you can define your own XML parameter parser which would allow you to describe multiple entities
|
100
100
|
# in a single request (i.e., by wrapping them all in a single root node), but if you just go with the flow
|
101
|
-
# and accept Rails' defaults, life will be much easier.
|
101
|
+
# and accept \Rails' defaults, life will be much easier.
|
102
102
|
#
|
103
103
|
# If you need to use a MIME type which isn't supported by default, you can register your own handlers in
|
104
104
|
# +config/initializers/mime_types.rb+ as follows.
|
105
105
|
#
|
106
|
-
# Mime::Type.register "image/
|
106
|
+
# Mime::Type.register "image/jpeg", :jpg
|
107
107
|
#
|
108
108
|
# +respond_to+ also allows you to specify a common block for different formats by using +any+:
|
109
109
|
#
|
@@ -289,7 +289,7 @@ module ActionController #:nodoc:
|
|
289
289
|
@format = request.negotiate_mime(@responses.keys)
|
290
290
|
end
|
291
291
|
|
292
|
-
class VariantCollector
|
292
|
+
class VariantCollector # :nodoc:
|
293
293
|
def initialize(variant = nil)
|
294
294
|
@variant = variant
|
295
295
|
@variants = {}
|
@@ -6,14 +6,19 @@ require "active_support/core_ext/module/anonymous"
|
|
6
6
|
require "action_dispatch/http/mime_type"
|
7
7
|
|
8
8
|
module ActionController
|
9
|
+
# = Action Controller Params Wrapper
|
10
|
+
#
|
9
11
|
# Wraps the parameters hash into a nested hash. This will allow clients to
|
10
12
|
# submit requests without having to specify any root elements.
|
11
13
|
#
|
12
|
-
# This functionality is enabled
|
13
|
-
#
|
14
|
+
# This functionality is enabled by default for JSON, and can be customized by
|
15
|
+
# setting the format array:
|
16
|
+
#
|
17
|
+
# class ApplicationController < ActionController::Base
|
18
|
+
# wrap_parameters format: [:json, :xml]
|
19
|
+
# end
|
14
20
|
#
|
15
|
-
# You could also turn it on per controller
|
16
|
-
# a non-empty array:
|
21
|
+
# You could also turn it on per controller:
|
17
22
|
#
|
18
23
|
# class UsersController < ApplicationController
|
19
24
|
# wrap_parameters format: [:json, :xml, :url_encoded_form, :multipart_form]
|
@@ -65,9 +70,15 @@ module ActionController
|
|
65
70
|
# class Admin::UsersController < ApplicationController
|
66
71
|
# end
|
67
72
|
#
|
68
|
-
# will try to check if
|
73
|
+
# will try to check if +Admin::User+ or +User+ model exists, and use it to
|
69
74
|
# determine the wrapper key respectively. If both models don't exist,
|
70
|
-
# it will then
|
75
|
+
# it will then fall back to use +user+ as the key.
|
76
|
+
#
|
77
|
+
# To disable this functionality for a controller:
|
78
|
+
#
|
79
|
+
# class UsersController < ApplicationController
|
80
|
+
# wrap_parameters false
|
81
|
+
# end
|
71
82
|
module ParamsWrapper
|
72
83
|
extend ActiveSupport::Concern
|
73
84
|
|
@@ -242,14 +253,14 @@ module ActionController
|
|
242
253
|
end
|
243
254
|
end
|
244
255
|
|
245
|
-
# Performs parameters wrapping upon the request. Called automatically
|
246
|
-
# by the metal call stack.
|
247
|
-
def process_action(*)
|
248
|
-
_perform_parameter_wrapping if _wrapper_enabled?
|
249
|
-
super
|
250
|
-
end
|
251
|
-
|
252
256
|
private
|
257
|
+
# Performs parameters wrapping upon the request. Called automatically
|
258
|
+
# by the metal call stack.
|
259
|
+
def process_action(*)
|
260
|
+
_perform_parameter_wrapping if _wrapper_enabled?
|
261
|
+
super
|
262
|
+
end
|
263
|
+
|
253
264
|
# Returns the wrapper key which will be used to store wrapped parameters.
|
254
265
|
def _wrapper_key
|
255
266
|
_wrapper_options.name
|
@@ -1,42 +1,33 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module ActionController
|
4
|
-
# HTTP Permissions Policy is a web standard for defining a mechanism to
|
5
|
-
# allow and deny the use of browser permissions in its own context, and
|
6
|
-
# in content within any <iframe> elements in the document.
|
7
|
-
#
|
8
|
-
# Full details of HTTP Permissions Policy specification and guidelines can
|
9
|
-
# be found at MDN:
|
10
|
-
#
|
11
|
-
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy
|
12
|
-
#
|
13
|
-
# Examples of usage:
|
14
|
-
#
|
15
|
-
# # Global policy
|
16
|
-
# Rails.application.config.permissions_policy do |f|
|
17
|
-
# f.camera :none
|
18
|
-
# f.gyroscope :none
|
19
|
-
# f.microphone :none
|
20
|
-
# f.usb :none
|
21
|
-
# f.fullscreen :self
|
22
|
-
# f.payment :self, "https://secure.example.com"
|
23
|
-
# end
|
24
|
-
#
|
25
|
-
# # Controller level policy
|
26
|
-
# class PagesController < ApplicationController
|
27
|
-
# permissions_policy do |p|
|
28
|
-
# p.geolocation "https://example.com"
|
29
|
-
# end
|
30
|
-
# end
|
3
|
+
module ActionController # :nodoc:
|
31
4
|
module PermissionsPolicy
|
32
5
|
extend ActiveSupport::Concern
|
33
6
|
|
34
7
|
module ClassMethods
|
8
|
+
# Overrides parts of the globally configured +Feature-Policy+
|
9
|
+
# header:
|
10
|
+
#
|
11
|
+
# class PagesController < ApplicationController
|
12
|
+
# permissions_policy do |policy|
|
13
|
+
# policy.geolocation "https://example.com"
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# Options can be passed similar to +before_action+. For example, pass
|
18
|
+
# <tt>only: :index</tt> to override the header on the index action only:
|
19
|
+
#
|
20
|
+
# class PagesController < ApplicationController
|
21
|
+
# permissions_policy(only: :index) do |policy|
|
22
|
+
# policy.camera :self
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
35
26
|
def permissions_policy(**options, &block)
|
36
27
|
before_action(options) do
|
37
28
|
if block_given?
|
38
29
|
policy = request.permissions_policy.clone
|
39
|
-
|
30
|
+
instance_exec(policy, &block)
|
40
31
|
request.permissions_policy = policy
|
41
32
|
end
|
42
33
|
end
|
@@ -7,9 +7,13 @@ module ActionController
|
|
7
7
|
include AbstractController::Logger
|
8
8
|
include ActionController::UrlFor
|
9
9
|
|
10
|
+
class UnsafeRedirectError < StandardError; end
|
11
|
+
|
10
12
|
ILLEGAL_HEADER_VALUE_REGEX = /[\x00-\x08\x0A-\x1F]/.freeze
|
11
13
|
|
12
|
-
|
14
|
+
included do
|
15
|
+
mattr_accessor :raise_on_open_redirects, default: false
|
16
|
+
end
|
13
17
|
|
14
18
|
# Redirects the browser to the target specified in +options+. This parameter can be any one of:
|
15
19
|
#
|
@@ -19,7 +23,7 @@ module ActionController
|
|
19
23
|
# * <tt>String</tt> not containing a protocol - The current protocol and host is prepended to the string.
|
20
24
|
# * <tt>Proc</tt> - A block that will be executed in the controller's context. Should return any option accepted by +redirect_to+.
|
21
25
|
#
|
22
|
-
# === Examples
|
26
|
+
# === Examples
|
23
27
|
#
|
24
28
|
# redirect_to action: "show", id: 5
|
25
29
|
# redirect_to @post
|
@@ -58,18 +62,44 @@ module ActionController
|
|
58
62
|
#
|
59
63
|
# Statements after +redirect_to+ in our controller get executed, so +redirect_to+ doesn't stop the execution of the function.
|
60
64
|
# To terminate the execution of the function immediately after the +redirect_to+, use return.
|
65
|
+
#
|
61
66
|
# redirect_to post_url(@post) and return
|
67
|
+
#
|
68
|
+
# === Open Redirect protection
|
69
|
+
#
|
70
|
+
# By default, \Rails protects against redirecting to external hosts for your app's safety, so called open redirects.
|
71
|
+
# Note: this was a new default in \Rails 7.0, after upgrading opt-in by uncommenting the line with +raise_on_open_redirects+ in <tt>config/initializers/new_framework_defaults_7_0.rb</tt>
|
72
|
+
#
|
73
|
+
# Here #redirect_to automatically validates the potentially-unsafe URL:
|
74
|
+
#
|
75
|
+
# redirect_to params[:redirect_url]
|
76
|
+
#
|
77
|
+
# Raises UnsafeRedirectError in the case of an unsafe redirect.
|
78
|
+
#
|
79
|
+
# To allow any external redirects pass <tt>allow_other_host: true</tt>, though using a user-provided param in that case is unsafe.
|
80
|
+
#
|
81
|
+
# redirect_to "https://rubyonrails.org", allow_other_host: true
|
82
|
+
#
|
83
|
+
# See #url_from for more information on what an internal and safe URL is, or how to fall back to an alternate redirect URL in the unsafe case.
|
62
84
|
def redirect_to(options = {}, response_options = {})
|
63
85
|
raise ActionControllerError.new("Cannot redirect to nil!") unless options
|
64
86
|
raise AbstractController::DoubleRenderError if response_body
|
65
87
|
|
66
|
-
|
88
|
+
allow_other_host = response_options.delete(:allow_other_host) { _allow_other_host }
|
89
|
+
|
90
|
+
self.status = _extract_redirect_to_status(options, response_options)
|
67
91
|
|
68
92
|
redirect_to_location = _compute_redirect_to_location(request, options)
|
69
93
|
_ensure_url_is_http_header_safe(redirect_to_location)
|
70
94
|
|
71
|
-
self.location = redirect_to_location
|
72
|
-
self.response_body = "
|
95
|
+
self.location = _enforce_open_redirect_protection(redirect_to_location, allow_other_host: allow_other_host)
|
96
|
+
self.response_body = ""
|
97
|
+
end
|
98
|
+
|
99
|
+
# Soft deprecated alias for #redirect_back_or_to where the +fallback_location+ location is supplied as a keyword argument instead
|
100
|
+
# of the first positional argument.
|
101
|
+
def redirect_back(fallback_location:, allow_other_host: _allow_other_host, **args)
|
102
|
+
redirect_back_or_to fallback_location, allow_other_host: allow_other_host, **args
|
73
103
|
end
|
74
104
|
|
75
105
|
# Redirects the browser to the page that issued the request (the referrer)
|
@@ -81,35 +111,37 @@ module ActionController
|
|
81
111
|
# subject to browser security settings and user preferences. If the request
|
82
112
|
# is missing this header, the <tt>fallback_location</tt> will be used.
|
83
113
|
#
|
84
|
-
#
|
85
|
-
#
|
86
|
-
#
|
87
|
-
#
|
88
|
-
#
|
89
|
-
#
|
90
|
-
#
|
114
|
+
# redirect_back_or_to({ action: "show", id: 5 })
|
115
|
+
# redirect_back_or_to @post
|
116
|
+
# redirect_back_or_to "http://www.rubyonrails.org"
|
117
|
+
# redirect_back_or_to "/images/screenshot.jpg"
|
118
|
+
# redirect_back_or_to posts_url
|
119
|
+
# redirect_back_or_to proc { edit_post_url(@post) }
|
120
|
+
# redirect_back_or_to '/', allow_other_host: false
|
91
121
|
#
|
92
122
|
# ==== Options
|
93
|
-
# * <tt>:fallback_location</tt> - The default fallback location that will be used on missing +Referer+ header.
|
94
123
|
# * <tt>:allow_other_host</tt> - Allow or disallow redirection to the host that is different to the current host, defaults to true.
|
95
124
|
#
|
96
125
|
# All other options that can be passed to #redirect_to are accepted as
|
97
|
-
# options and the behavior is identical.
|
98
|
-
def
|
99
|
-
referer
|
100
|
-
|
101
|
-
|
126
|
+
# options, and the behavior is identical.
|
127
|
+
def redirect_back_or_to(fallback_location, allow_other_host: _allow_other_host, **options)
|
128
|
+
if request.referer && (allow_other_host || _url_host_allowed?(request.referer))
|
129
|
+
redirect_to request.referer, allow_other_host: allow_other_host, **options
|
130
|
+
else
|
131
|
+
# The method level `allow_other_host` doesn't apply in the fallback case, omit and let the `redirect_to` handling take over.
|
132
|
+
redirect_to fallback_location, **options
|
133
|
+
end
|
102
134
|
end
|
103
135
|
|
104
|
-
def _compute_redirect_to_location(request, options)
|
136
|
+
def _compute_redirect_to_location(request, options) # :nodoc:
|
105
137
|
case options
|
106
138
|
# The scheme name consist of a letter followed by any combination of
|
107
139
|
# letters, digits, and the plus ("+"), period ("."), or hyphen ("-")
|
108
140
|
# characters; and is terminated by a colon (":").
|
109
141
|
# See https://tools.ietf.org/html/rfc3986#section-3.1
|
110
142
|
# The protocol relative scheme starts with a double slash "//".
|
111
|
-
when /\A([a-z][a-z\d
|
112
|
-
options
|
143
|
+
when /\A([a-z][a-z\d\-+.]*:|\/\/).*/i
|
144
|
+
options.to_str
|
113
145
|
when String
|
114
146
|
request.protocol + request.host_with_port + options
|
115
147
|
when Proc
|
@@ -121,7 +153,35 @@ module ActionController
|
|
121
153
|
module_function :_compute_redirect_to_location
|
122
154
|
public :_compute_redirect_to_location
|
123
155
|
|
156
|
+
# Verifies the passed +location+ is an internal URL that's safe to redirect to and returns it, or nil if not.
|
157
|
+
# Useful to wrap a params provided redirect URL and fall back to an alternate URL to redirect to:
|
158
|
+
#
|
159
|
+
# redirect_to url_from(params[:redirect_url]) || root_url
|
160
|
+
#
|
161
|
+
# The +location+ is considered internal, and safe, if it's on the same host as <tt>request.host</tt>:
|
162
|
+
#
|
163
|
+
# # If request.host is example.com:
|
164
|
+
# url_from("https://example.com/profile") # => "https://example.com/profile"
|
165
|
+
# url_from("http://example.com/profile") # => "http://example.com/profile"
|
166
|
+
# url_from("http://evil.com/profile") # => nil
|
167
|
+
#
|
168
|
+
# Subdomains are considered part of the host:
|
169
|
+
#
|
170
|
+
# # If request.host is on https://example.com or https://app.example.com, you'd get:
|
171
|
+
# url_from("https://dev.example.com/profile") # => nil
|
172
|
+
#
|
173
|
+
# NOTE: there's a similarity with {url_for}[rdoc-ref:ActionDispatch::Routing::UrlFor#url_for], which generates an internal URL from various options from within the app, e.g. <tt>url_for(@post)</tt>.
|
174
|
+
# However, #url_from is meant to take an external parameter to verify as in <tt>url_from(params[:redirect_url])</tt>.
|
175
|
+
def url_from(location)
|
176
|
+
location = location.presence
|
177
|
+
location if location && _url_host_allowed?(location)
|
178
|
+
end
|
179
|
+
|
124
180
|
private
|
181
|
+
def _allow_other_host
|
182
|
+
!raise_on_open_redirects
|
183
|
+
end
|
184
|
+
|
125
185
|
def _extract_redirect_to_status(options, response_options)
|
126
186
|
if options.is_a?(Hash) && options.key?(:status)
|
127
187
|
Rack::Utils.status_code(options.delete(:status))
|
@@ -132,8 +192,21 @@ module ActionController
|
|
132
192
|
end
|
133
193
|
end
|
134
194
|
|
195
|
+
def _enforce_open_redirect_protection(location, allow_other_host:)
|
196
|
+
if allow_other_host || _url_host_allowed?(location)
|
197
|
+
location
|
198
|
+
else
|
199
|
+
raise UnsafeRedirectError, "Unsafe redirect to #{location.truncate(100).inspect}, pass allow_other_host: true to redirect anyway."
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
135
203
|
def _url_host_allowed?(url)
|
136
|
-
URI(url.to_s).host
|
204
|
+
host = URI(url.to_s).host
|
205
|
+
|
206
|
+
return true if host == request.host
|
207
|
+
return false unless host.nil?
|
208
|
+
return false unless url.to_s.start_with?("/")
|
209
|
+
!url.to_s.start_with?("//")
|
137
210
|
rescue ArgumentError, URI::Error
|
138
211
|
false
|
139
212
|
end
|
@@ -142,7 +215,7 @@ module ActionController
|
|
142
215
|
# Attempt to comply with the set of valid token characters
|
143
216
|
# defined for an HTTP header value in
|
144
217
|
# https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6
|
145
|
-
if url.match(ILLEGAL_HEADER_VALUE_REGEX)
|
218
|
+
if url.match?(ILLEGAL_HEADER_VALUE_REGEX)
|
146
219
|
msg = "The redirect URL #{url} contains one or more illegal HTTP header field character. " \
|
147
220
|
"Set of legal characters defined in https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6"
|
148
221
|
raise UnsafeRedirectError, msg
|