actionpack 6.1.7.6 → 7.0.0.alpha1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +99 -584
- data/MIT-LICENSE +2 -1
- data/README.rdoc +2 -3
- data/lib/abstract_controller/asset_paths.rb +1 -1
- data/lib/abstract_controller/base.rb +7 -21
- data/lib/abstract_controller/caching/fragments.rb +2 -2
- data/lib/abstract_controller/caching.rb +1 -1
- data/lib/abstract_controller/callbacks.rb +9 -8
- data/lib/abstract_controller/collector.rb +2 -2
- data/lib/abstract_controller/error.rb +1 -1
- data/lib/abstract_controller/helpers.rb +3 -2
- data/lib/abstract_controller/logger.rb +1 -1
- data/lib/abstract_controller/railties/routes_helpers.rb +2 -0
- data/lib/abstract_controller/translation.rb +0 -2
- data/lib/abstract_controller/url_for.rb +4 -6
- data/lib/action_controller/api.rb +1 -1
- data/lib/action_controller/log_subscriber.rb +3 -1
- data/lib/action_controller/metal/conditional_get.rb +38 -1
- data/lib/action_controller/metal/content_security_policy.rb +1 -1
- data/lib/action_controller/metal/cookies.rb +1 -1
- data/lib/action_controller/metal/data_streaming.rb +5 -13
- data/lib/action_controller/metal/exceptions.rb +19 -30
- data/lib/action_controller/metal/flash.rb +6 -2
- data/lib/action_controller/metal/http_authentication.rb +15 -16
- data/lib/action_controller/metal/instrumentation.rb +55 -52
- data/lib/action_controller/metal/live.rb +42 -2
- data/lib/action_controller/metal/mime_responds.rb +3 -3
- data/lib/action_controller/metal/params_wrapper.rb +7 -7
- data/lib/action_controller/metal/permissions_policy.rb +1 -1
- data/lib/action_controller/metal/query_tags.rb +16 -0
- data/lib/action_controller/metal/redirecting.rb +49 -34
- data/lib/action_controller/metal/rendering.rb +9 -9
- data/lib/action_controller/metal/request_forgery_protection.rb +64 -20
- data/lib/action_controller/metal/rescue.rb +1 -1
- data/lib/action_controller/metal/streaming.rb +1 -3
- data/lib/action_controller/metal/strong_parameters.rb +25 -29
- data/lib/action_controller/metal/testing.rb +0 -2
- data/lib/action_controller/metal.rb +7 -10
- data/lib/action_controller/railtie.rb +42 -5
- data/lib/action_controller/test_case.rb +6 -2
- data/lib/action_controller.rb +2 -5
- data/lib/action_dispatch/http/cache.rb +14 -7
- data/lib/action_dispatch/http/content_security_policy.rb +47 -37
- data/lib/action_dispatch/http/filter_parameters.rb +5 -0
- data/lib/action_dispatch/http/mime_negotiation.rb +13 -3
- data/lib/action_dispatch/http/mime_type.rb +9 -11
- data/lib/action_dispatch/http/parameters.rb +4 -4
- data/lib/action_dispatch/http/permissions_policy.rb +1 -1
- data/lib/action_dispatch/http/request.rb +10 -19
- data/lib/action_dispatch/http/response.rb +3 -3
- data/lib/action_dispatch/http/url.rb +9 -10
- 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 +22 -13
- data/lib/action_dispatch/journey/route.rb +5 -12
- data/lib/action_dispatch/journey/router/utils.rb +2 -2
- data/lib/action_dispatch/journey/router.rb +1 -1
- data/lib/action_dispatch/journey/routes.rb +3 -3
- 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/middleware/actionable_exceptions.rb +0 -1
- data/lib/action_dispatch/middleware/cookies.rb +27 -31
- data/lib/action_dispatch/middleware/debug_exceptions.rb +6 -4
- data/lib/action_dispatch/middleware/debug_locks.rb +3 -3
- data/lib/action_dispatch/middleware/exception_wrapper.rb +4 -0
- data/lib/action_dispatch/middleware/executor.rb +1 -1
- data/lib/action_dispatch/middleware/flash.rb +9 -11
- data/lib/action_dispatch/middleware/host_authorization.rb +25 -73
- data/lib/action_dispatch/middleware/remote_ip.rb +16 -4
- data/lib/action_dispatch/middleware/session/abstract_store.rb +1 -1
- data/lib/action_dispatch/middleware/show_exceptions.rb +6 -18
- data/lib/action_dispatch/middleware/stack.rb +50 -9
- data/lib/action_dispatch/middleware/static.rb +2 -5
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +4 -11
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +28 -18
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +5 -14
- data/lib/action_dispatch/railtie.rb +8 -2
- data/lib/action_dispatch/request/session.rb +43 -13
- data/lib/action_dispatch/routing/mapper.rb +44 -72
- data/lib/action_dispatch/routing/redirection.rb +0 -2
- data/lib/action_dispatch/routing/route_set.rb +7 -4
- data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
- data/lib/action_dispatch/routing/url_for.rb +1 -2
- data/lib/action_dispatch/routing.rb +2 -2
- data/lib/action_dispatch/system_test_case.rb +6 -12
- data/lib/action_dispatch/system_testing/driver.rb +24 -4
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +10 -6
- data/lib/action_dispatch/testing/assertions.rb +2 -5
- data/lib/action_dispatch/testing/integration.rb +6 -8
- data/lib/action_dispatch/testing/test_process.rb +2 -2
- data/lib/action_dispatch.rb +1 -1
- data/lib/action_pack/gem_version.rb +4 -4
- data/lib/action_pack.rb +1 -1
- metadata +21 -21
@@ -16,30 +16,6 @@ module ActionController
|
|
16
16
|
|
17
17
|
attr_internal :view_runtime
|
18
18
|
|
19
|
-
def process_action(*)
|
20
|
-
raw_payload = {
|
21
|
-
controller: self.class.name,
|
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
|
41
|
-
end
|
42
|
-
|
43
19
|
def render(*)
|
44
20
|
render_output = nil
|
45
21
|
self.view_runtime = cleanup_view_runtime do
|
@@ -70,37 +46,64 @@ module ActionController
|
|
70
46
|
end
|
71
47
|
end
|
72
48
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
49
|
+
private
|
50
|
+
def process_action(*)
|
51
|
+
raw_payload = {
|
52
|
+
controller: self.class.name,
|
53
|
+
action: action_name,
|
54
|
+
request: request,
|
55
|
+
params: request.filtered_parameters,
|
56
|
+
headers: request.headers,
|
57
|
+
format: request.format.ref,
|
58
|
+
method: request.request_method,
|
59
|
+
path: request.fullpath
|
60
|
+
}
|
78
61
|
|
79
|
-
|
80
|
-
# views, like database querying time.
|
81
|
-
#
|
82
|
-
# def cleanup_view_runtime
|
83
|
-
# super - time_taken_in_something_expensive
|
84
|
-
# end
|
85
|
-
def cleanup_view_runtime # :doc:
|
86
|
-
yield
|
87
|
-
end
|
62
|
+
ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload)
|
88
63
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
64
|
+
ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
|
65
|
+
result = super
|
66
|
+
payload[:response] = response
|
67
|
+
payload[:status] = response.status
|
68
|
+
result
|
69
|
+
rescue => error
|
70
|
+
payload[:status] = ActionDispatch::ExceptionWrapper.status_code_for_exception(error.class.name)
|
71
|
+
raise
|
72
|
+
ensure
|
73
|
+
append_info_to_payload(payload)
|
74
|
+
end
|
75
|
+
end
|
94
76
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
77
|
+
# A hook invoked every time a before callback is halted.
|
78
|
+
def halted_callback_hook(filter, _)
|
79
|
+
ActiveSupport::Notifications.instrument("halted_callback.action_controller", filter: filter)
|
80
|
+
end
|
81
|
+
|
82
|
+
# A hook which allows you to clean up any time, wrongly taken into account in
|
83
|
+
# views, like database querying time.
|
84
|
+
#
|
85
|
+
# def cleanup_view_runtime
|
86
|
+
# super - time_taken_in_something_expensive
|
87
|
+
# end
|
88
|
+
def cleanup_view_runtime # :doc:
|
89
|
+
yield
|
90
|
+
end
|
91
|
+
|
92
|
+
# Every time after an action is processed, this method is invoked
|
93
|
+
# with the payload, so you can add more information.
|
94
|
+
def append_info_to_payload(payload) # :doc:
|
95
|
+
payload[:view_runtime] = view_runtime
|
96
|
+
end
|
97
|
+
|
98
|
+
module ClassMethods
|
99
|
+
# A hook which allows other frameworks to log what happened during
|
100
|
+
# controller process action. This method should return an array
|
101
|
+
# with the messages to be added.
|
102
|
+
def log_process_action(payload) # :nodoc:
|
103
|
+
messages, view_runtime = [], payload[:view_runtime]
|
104
|
+
messages << ("Views: %.1fms" % view_runtime.to_f) if view_runtime
|
105
|
+
messages
|
106
|
+
end
|
103
107
|
end
|
104
|
-
end
|
105
108
|
end
|
106
109
|
end
|
@@ -124,7 +124,7 @@ module ActionController
|
|
124
124
|
class ClientDisconnected < RuntimeError
|
125
125
|
end
|
126
126
|
|
127
|
-
class Buffer < ActionDispatch::Response::Buffer
|
127
|
+
class Buffer < ActionDispatch::Response::Buffer # :nodoc:
|
128
128
|
include MonitorMixin
|
129
129
|
|
130
130
|
class << self
|
@@ -168,6 +168,11 @@ module ActionController
|
|
168
168
|
end
|
169
169
|
end
|
170
170
|
|
171
|
+
# Same as +write+ but automatically include a newline at the end of the string.
|
172
|
+
def writeln(string)
|
173
|
+
write string.end_with?("\n") ? string : "#{string}\n"
|
174
|
+
end
|
175
|
+
|
171
176
|
# Write a 'close' event to the buffer; the producer/writing thread
|
172
177
|
# uses this to notify us that it's finished supplying content.
|
173
178
|
#
|
@@ -225,7 +230,7 @@ module ActionController
|
|
225
230
|
end
|
226
231
|
end
|
227
232
|
|
228
|
-
class Response < ActionDispatch::Response
|
233
|
+
class Response < ActionDispatch::Response # :nodoc: all
|
229
234
|
private
|
230
235
|
def before_committed
|
231
236
|
super
|
@@ -291,6 +296,41 @@ module ActionController
|
|
291
296
|
response.close if response
|
292
297
|
end
|
293
298
|
|
299
|
+
# Sends a stream to the browser, which is helpful when you're generating exports or other running data where you
|
300
|
+
# don't want the entire file buffered in memory first. Similar to send_data, but where the data is generated live.
|
301
|
+
#
|
302
|
+
# Options:
|
303
|
+
# * <tt>:filename</tt> - suggests a filename for the browser to use.
|
304
|
+
# * <tt>:type</tt> - specifies an HTTP content type.
|
305
|
+
# You can specify either a string or a symbol for a registered type with <tt>Mime::Type.register</tt>, for example :json.
|
306
|
+
# If omitted, type will be inferred from the file extension specified in <tt>:filename</tt>.
|
307
|
+
# If no content type is registered for the extension, the default type 'application/octet-stream' will be used.
|
308
|
+
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
|
309
|
+
# Valid values are 'inline' and 'attachment' (default).
|
310
|
+
#
|
311
|
+
# Example of generating a csv export:
|
312
|
+
#
|
313
|
+
# send_stream(filename: "subscribers.csv") do |stream|
|
314
|
+
# stream.write "email_address,updated_at\n"
|
315
|
+
#
|
316
|
+
# @subscribers.find_each do |subscriber|
|
317
|
+
# stream.write "#{subscriber.email_address},#{subscriber.updated_at}\n"
|
318
|
+
# end
|
319
|
+
# end
|
320
|
+
def send_stream(filename:, disposition: "attachment", type: nil)
|
321
|
+
response.headers["Content-Type"] =
|
322
|
+
(type.is_a?(Symbol) ? Mime[type].to_s : type) ||
|
323
|
+
Mime::Type.lookup_by_extension(File.extname(filename).downcase.delete(".")) ||
|
324
|
+
"application/octet-stream"
|
325
|
+
|
326
|
+
response.headers["Content-Disposition"] =
|
327
|
+
ActionDispatch::Http::ContentDisposition.format(disposition: disposition, filename: filename)
|
328
|
+
|
329
|
+
yield response.stream
|
330
|
+
ensure
|
331
|
+
response.stream.close
|
332
|
+
end
|
333
|
+
|
294
334
|
private
|
295
335
|
# Spawn a new thread to serve up the controller in. This is to get
|
296
336
|
# 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:
|
@@ -103,7 +103,7 @@ module ActionController #:nodoc:
|
|
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 = {}
|
@@ -242,14 +242,14 @@ module ActionController
|
|
242
242
|
end
|
243
243
|
end
|
244
244
|
|
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
245
|
private
|
246
|
+
# Performs parameters wrapping upon the request. Called automatically
|
247
|
+
# by the metal call stack.
|
248
|
+
def process_action(*)
|
249
|
+
_perform_parameter_wrapping if _wrapper_enabled?
|
250
|
+
super
|
251
|
+
end
|
252
|
+
|
253
253
|
# Returns the wrapper key which will be used to store wrapped parameters.
|
254
254
|
def _wrapper_key
|
255
255
|
_wrapper_options.name
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module ActionController
|
3
|
+
module ActionController # :nodoc:
|
4
4
|
# HTTP Permissions Policy is a web standard for defining a mechanism to
|
5
5
|
# allow and deny the use of browser permissions in its own context, and
|
6
6
|
# in content within any <iframe> elements in the document.
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionController
|
4
|
+
module QueryTags # :nodoc:
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
around_action :expose_controller_to_query_logs
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
def expose_controller_to_query_logs(&block)
|
13
|
+
ActiveRecord::QueryLogs.set_context(controller: self, &block)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -7,9 +7,9 @@ module ActionController
|
|
7
7
|
include AbstractController::Logger
|
8
8
|
include ActionController::UrlFor
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
included do
|
11
|
+
mattr_accessor :raise_on_open_redirects, default: false
|
12
|
+
end
|
13
13
|
|
14
14
|
# Redirects the browser to the target specified in +options+. This parameter can be any one of:
|
15
15
|
#
|
@@ -58,20 +58,28 @@ module ActionController
|
|
58
58
|
#
|
59
59
|
# Statements after +redirect_to+ in our controller get executed, so +redirect_to+ doesn't stop the execution of the function.
|
60
60
|
# To terminate the execution of the function immediately after the +redirect_to+, use return.
|
61
|
+
#
|
61
62
|
# redirect_to post_url(@post) and return
|
63
|
+
#
|
64
|
+
# Passing user input directly into +redirect_to+ is considered dangerous (e.g. `redirect_to(params[:location])`).
|
65
|
+
# Always use regular expressions or a permitted list when redirecting to a user specified location.
|
62
66
|
def redirect_to(options = {}, response_options = {})
|
67
|
+
response_options[:allow_other_host] ||= _allow_other_host unless response_options.key?(:allow_other_host)
|
68
|
+
|
63
69
|
raise ActionControllerError.new("Cannot redirect to nil!") unless options
|
64
70
|
raise AbstractController::DoubleRenderError if response_body
|
65
71
|
|
66
72
|
self.status = _extract_redirect_to_status(options, response_options)
|
67
|
-
|
68
|
-
redirect_to_location = _compute_redirect_to_location(request, options)
|
69
|
-
_ensure_url_is_http_header_safe(redirect_to_location)
|
70
|
-
|
71
|
-
self.location = redirect_to_location
|
73
|
+
self.location = _compute_safe_redirect_to_location(request, options, response_options)
|
72
74
|
self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(response.location)}\">redirected</a>.</body></html>"
|
73
75
|
end
|
74
76
|
|
77
|
+
# Soft deprecated alias for <tt>redirect_back_or_to</tt> where the fallback_location location is supplied as a keyword argument instead
|
78
|
+
# of the first positional argument.
|
79
|
+
def redirect_back(fallback_location:, allow_other_host: _allow_other_host, **args)
|
80
|
+
redirect_back_or_to fallback_location, allow_other_host: allow_other_host, **args
|
81
|
+
end
|
82
|
+
|
75
83
|
# Redirects the browser to the page that issued the request (the referrer)
|
76
84
|
# if possible, otherwise redirects to the provided default fallback
|
77
85
|
# location.
|
@@ -81,35 +89,49 @@ module ActionController
|
|
81
89
|
# subject to browser security settings and user preferences. If the request
|
82
90
|
# is missing this header, the <tt>fallback_location</tt> will be used.
|
83
91
|
#
|
84
|
-
#
|
85
|
-
#
|
86
|
-
#
|
87
|
-
#
|
88
|
-
#
|
89
|
-
#
|
90
|
-
#
|
92
|
+
# redirect_back_or_to({ action: "show", id: 5 })
|
93
|
+
# redirect_back_or_to @post
|
94
|
+
# redirect_back_or_to "http://www.rubyonrails.org"
|
95
|
+
# redirect_back_or_to "/images/screenshot.jpg"
|
96
|
+
# redirect_back_or_to posts_url
|
97
|
+
# redirect_back_or_to proc { edit_post_url(@post) }
|
98
|
+
# redirect_back_or_to '/', allow_other_host: false
|
91
99
|
#
|
92
100
|
# ==== Options
|
93
|
-
# * <tt>:fallback_location</tt> - The default fallback location that will be used on missing +Referer+ header.
|
94
101
|
# * <tt>:allow_other_host</tt> - Allow or disallow redirection to the host that is different to the current host, defaults to true.
|
95
102
|
#
|
96
103
|
# All other options that can be passed to #redirect_to are accepted as
|
97
104
|
# options and the behavior is identical.
|
98
|
-
def
|
99
|
-
|
100
|
-
|
101
|
-
|
105
|
+
def redirect_back_or_to(fallback_location, allow_other_host: _allow_other_host, **options)
|
106
|
+
location = request.referer || fallback_location
|
107
|
+
location = fallback_location unless allow_other_host || _url_host_allowed?(request.referer)
|
108
|
+
allow_other_host = true if _allow_other_host && !allow_other_host # if the fallback is an open redirect
|
109
|
+
|
110
|
+
redirect_to location, allow_other_host: allow_other_host, **options
|
102
111
|
end
|
103
112
|
|
104
|
-
def
|
113
|
+
def _compute_safe_redirect_to_location(request, options, response_options)
|
114
|
+
location = _compute_redirect_to_location(request, options)
|
115
|
+
|
116
|
+
if response_options[:allow_other_host] || _url_host_allowed?(location)
|
117
|
+
location
|
118
|
+
else
|
119
|
+
raise(ArgumentError, <<~MSG.squish)
|
120
|
+
Unsafe redirect #{location.truncate(100).inspect},
|
121
|
+
use :allow_other_host to redirect anyway.
|
122
|
+
MSG
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def _compute_redirect_to_location(request, options) # :nodoc:
|
105
127
|
case options
|
106
128
|
# The scheme name consist of a letter followed by any combination of
|
107
129
|
# letters, digits, and the plus ("+"), period ("."), or hyphen ("-")
|
108
130
|
# characters; and is terminated by a colon (":").
|
109
131
|
# See https://tools.ietf.org/html/rfc3986#section-3.1
|
110
132
|
# The protocol relative scheme starts with a double slash "//".
|
111
|
-
when /\A([a-z][a-z\d
|
112
|
-
options
|
133
|
+
when /\A([a-z][a-z\d\-+.]*:|\/\/).*/i
|
134
|
+
options.to_str
|
113
135
|
when String
|
114
136
|
request.protocol + request.host_with_port + options
|
115
137
|
when Proc
|
@@ -122,6 +144,10 @@ module ActionController
|
|
122
144
|
public :_compute_redirect_to_location
|
123
145
|
|
124
146
|
private
|
147
|
+
def _allow_other_host
|
148
|
+
!raise_on_open_redirects
|
149
|
+
end
|
150
|
+
|
125
151
|
def _extract_redirect_to_status(options, response_options)
|
126
152
|
if options.is_a?(Hash) && options.key?(:status)
|
127
153
|
Rack::Utils.status_code(options.delete(:status))
|
@@ -137,16 +163,5 @@ module ActionController
|
|
137
163
|
rescue ArgumentError, URI::Error
|
138
164
|
false
|
139
165
|
end
|
140
|
-
|
141
|
-
def _ensure_url_is_http_header_safe(url)
|
142
|
-
# Attempt to comply with the set of valid token characters
|
143
|
-
# defined for an HTTP header value in
|
144
|
-
# https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6
|
145
|
-
if url.match(ILLEGAL_HEADER_VALUE_REGEX)
|
146
|
-
msg = "The redirect URL #{url} contains one or more illegal HTTP header field character. " \
|
147
|
-
"Set of legal characters defined in https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6"
|
148
|
-
raise UnsafeRedirectError, msg
|
149
|
-
end
|
150
|
-
end
|
151
166
|
end
|
152
167
|
end
|
@@ -24,14 +24,8 @@ module ActionController
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
# Before processing, set the request formats in current controller formats.
|
28
|
-
def process_action(*) #:nodoc:
|
29
|
-
self.formats = request.formats.map(&:ref).compact
|
30
|
-
super
|
31
|
-
end
|
32
|
-
|
33
27
|
# Check for double render errors and set the content_type after rendering.
|
34
|
-
def render(*args)
|
28
|
+
def render(*args) # :nodoc:
|
35
29
|
raise ::AbstractController::DoubleRenderError if response_body
|
36
30
|
super
|
37
31
|
end
|
@@ -53,6 +47,12 @@ module ActionController
|
|
53
47
|
end
|
54
48
|
|
55
49
|
private
|
50
|
+
# Before processing, set the request formats in current controller formats.
|
51
|
+
def process_action(*) # :nodoc:
|
52
|
+
self.formats = request.formats.filter_map(&:ref)
|
53
|
+
super
|
54
|
+
end
|
55
|
+
|
56
56
|
def _process_variant(options)
|
57
57
|
if defined?(request) && !request.nil? && request.variant.present?
|
58
58
|
options[:variant] = request.variant
|
@@ -78,8 +78,8 @@ module ActionController
|
|
78
78
|
end
|
79
79
|
|
80
80
|
def _set_vary_header
|
81
|
-
if
|
82
|
-
|
81
|
+
if self.headers["Vary"].blank? && request.should_apply_vary_header?
|
82
|
+
self.headers["Vary"] = "Accept"
|
83
83
|
end
|
84
84
|
end
|
85
85
|
|
@@ -4,11 +4,11 @@ require "rack/session/abstract/id"
|
|
4
4
|
require "action_controller/metal/exceptions"
|
5
5
|
require "active_support/security_utils"
|
6
6
|
|
7
|
-
module ActionController
|
8
|
-
class InvalidAuthenticityToken < ActionControllerError
|
7
|
+
module ActionController # :nodoc:
|
8
|
+
class InvalidAuthenticityToken < ActionControllerError # :nodoc:
|
9
9
|
end
|
10
10
|
|
11
|
-
class InvalidCrossOriginRequest < ActionControllerError
|
11
|
+
class InvalidCrossOriginRequest < ActionControllerError # :nodoc:
|
12
12
|
end
|
13
13
|
|
14
14
|
# Controller actions are protected from Cross-Site Request Forgery (CSRF) attacks
|
@@ -57,6 +57,17 @@ module ActionController #:nodoc:
|
|
57
57
|
module RequestForgeryProtection
|
58
58
|
extend ActiveSupport::Concern
|
59
59
|
|
60
|
+
class DisabledSessionError < StandardError
|
61
|
+
MESSAGE = <<~EOS.squish
|
62
|
+
Request forgery protection requires a working session store but your application has sessions disabled.
|
63
|
+
You need to either disable request forgery protection, or configure a working session store.
|
64
|
+
EOS
|
65
|
+
|
66
|
+
def initialize(message = MESSAGE)
|
67
|
+
super
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
60
71
|
include AbstractController::Helpers
|
61
72
|
include AbstractController::Callbacks
|
62
73
|
|
@@ -90,6 +101,11 @@ module ActionController #:nodoc:
|
|
90
101
|
config_accessor :default_protect_from_forgery
|
91
102
|
self.default_protect_from_forgery = false
|
92
103
|
|
104
|
+
# Controls whether trying to use forgery protection without a working session store
|
105
|
+
# issues a warning or raises an error.
|
106
|
+
config_accessor :silence_disabled_session_errors
|
107
|
+
self.silence_disabled_session_errors = true
|
108
|
+
|
93
109
|
# Controls whether URL-safe CSRF tokens are generated.
|
94
110
|
config_accessor :urlsafe_csrf_tokens, instance_writer: false
|
95
111
|
self.urlsafe_csrf_tokens = false
|
@@ -170,7 +186,7 @@ module ActionController #:nodoc:
|
|
170
186
|
end
|
171
187
|
|
172
188
|
private
|
173
|
-
class NullSessionHash < Rack::Session::Abstract::SessionHash
|
189
|
+
class NullSessionHash < Rack::Session::Abstract::SessionHash # :nodoc:
|
174
190
|
def initialize(req)
|
175
191
|
super(nil, req)
|
176
192
|
@data = {}
|
@@ -183,9 +199,13 @@ module ActionController #:nodoc:
|
|
183
199
|
def exists?
|
184
200
|
true
|
185
201
|
end
|
202
|
+
|
203
|
+
def enabled?
|
204
|
+
false
|
205
|
+
end
|
186
206
|
end
|
187
207
|
|
188
|
-
class NullCookieJar < ActionDispatch::Cookies::CookieJar
|
208
|
+
class NullCookieJar < ActionDispatch::Cookies::CookieJar # :nodoc:
|
189
209
|
def write(*)
|
190
210
|
# nothing
|
191
211
|
end
|
@@ -203,12 +223,14 @@ module ActionController #:nodoc:
|
|
203
223
|
end
|
204
224
|
|
205
225
|
class Exception
|
226
|
+
attr_accessor :warning_message
|
227
|
+
|
206
228
|
def initialize(controller)
|
207
229
|
@controller = controller
|
208
230
|
end
|
209
231
|
|
210
232
|
def handle_unverified_request
|
211
|
-
raise ActionController::InvalidAuthenticityToken
|
233
|
+
raise ActionController::InvalidAuthenticityToken, warning_message
|
212
234
|
end
|
213
235
|
end
|
214
236
|
end
|
@@ -228,22 +250,31 @@ module ActionController #:nodoc:
|
|
228
250
|
mark_for_same_origin_verification!
|
229
251
|
|
230
252
|
if !verified_request?
|
231
|
-
if logger && log_warning_on_csrf_failure
|
232
|
-
|
233
|
-
logger.warn "Can't verify CSRF token authenticity."
|
234
|
-
else
|
235
|
-
logger.warn "HTTP Origin header (#{request.origin}) didn't match request.base_url (#{request.base_url})"
|
236
|
-
end
|
237
|
-
end
|
253
|
+
logger.warn unverified_request_warning_message if logger && log_warning_on_csrf_failure
|
254
|
+
|
238
255
|
handle_unverified_request
|
239
256
|
end
|
240
257
|
end
|
241
258
|
|
242
259
|
def handle_unverified_request # :doc:
|
243
|
-
forgery_protection_strategy.new(self)
|
260
|
+
protection_strategy = forgery_protection_strategy.new(self)
|
261
|
+
|
262
|
+
if protection_strategy.respond_to?(:warning_message)
|
263
|
+
protection_strategy.warning_message = unverified_request_warning_message
|
264
|
+
end
|
265
|
+
|
266
|
+
protection_strategy.handle_unverified_request
|
267
|
+
end
|
268
|
+
|
269
|
+
def unverified_request_warning_message # :nodoc:
|
270
|
+
if valid_request_origin?
|
271
|
+
"Can't verify CSRF token authenticity."
|
272
|
+
else
|
273
|
+
"HTTP Origin header (#{request.origin}) didn't match request.base_url (#{request.base_url})"
|
274
|
+
end
|
244
275
|
end
|
245
276
|
|
246
|
-
|
277
|
+
# :nodoc:
|
247
278
|
CROSS_ORIGIN_JAVASCRIPT_WARNING = "Security warning: an embedded " \
|
248
279
|
"<script> tag on another site requested protected JavaScript. " \
|
249
280
|
"If you know what you're doing, go ahead and disable forgery " \
|
@@ -303,15 +334,15 @@ module ActionController #:nodoc:
|
|
303
334
|
[form_authenticity_param, request.x_csrf_token]
|
304
335
|
end
|
305
336
|
|
306
|
-
#
|
307
|
-
def form_authenticity_token(form_options: {})
|
337
|
+
# Creates the authenticity token for the current request.
|
338
|
+
def form_authenticity_token(form_options: {}) # :doc:
|
308
339
|
masked_authenticity_token(session, form_options: form_options)
|
309
340
|
end
|
310
341
|
|
311
342
|
# Creates a masked version of the authenticity token that varies
|
312
343
|
# on each request. The masking is used to mitigate SSL attacks
|
313
344
|
# like BREACH.
|
314
|
-
def masked_authenticity_token(session, form_options: {})
|
345
|
+
def masked_authenticity_token(session, form_options: {})
|
315
346
|
action, method = form_options.values_at(:action, :method)
|
316
347
|
|
317
348
|
raw_token = if per_form_csrf_tokens && action && method
|
@@ -438,7 +469,20 @@ module ActionController #:nodoc:
|
|
438
469
|
|
439
470
|
# Checks if the controller allows forgery protection.
|
440
471
|
def protect_against_forgery? # :doc:
|
441
|
-
allow_forgery_protection
|
472
|
+
allow_forgery_protection && ensure_session_is_enabled!
|
473
|
+
end
|
474
|
+
|
475
|
+
def ensure_session_is_enabled!
|
476
|
+
if !session.respond_to?(:enabled?) || session.enabled?
|
477
|
+
true
|
478
|
+
else
|
479
|
+
if silence_disabled_session_errors
|
480
|
+
ActiveSupport::Deprecation.warn(DisabledSessionError::MESSAGE)
|
481
|
+
false
|
482
|
+
else
|
483
|
+
raise DisabledSessionError
|
484
|
+
end
|
485
|
+
end
|
442
486
|
end
|
443
487
|
|
444
488
|
NULL_ORIGIN_MESSAGE = <<~MSG
|
@@ -469,7 +513,7 @@ module ActionController #:nodoc:
|
|
469
513
|
|
470
514
|
def generate_csrf_token # :nodoc:
|
471
515
|
if urlsafe_csrf_tokens
|
472
|
-
SecureRandom.urlsafe_base64(AUTHENTICITY_TOKEN_LENGTH
|
516
|
+
SecureRandom.urlsafe_base64(AUTHENTICITY_TOKEN_LENGTH)
|
473
517
|
else
|
474
518
|
SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
|
475
519
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require "rack/chunked"
|
4
4
|
|
5
|
-
module ActionController
|
5
|
+
module ActionController # :nodoc:
|
6
6
|
# Allows views to be streamed back to the client as they are rendered.
|
7
7
|
#
|
8
8
|
# By default, Rails renders views by first rendering the template
|
@@ -193,8 +193,6 @@ module ActionController #:nodoc:
|
|
193
193
|
# To be described.
|
194
194
|
#
|
195
195
|
module Streaming
|
196
|
-
extend ActiveSupport::Concern
|
197
|
-
|
198
196
|
private
|
199
197
|
# Set proper cache control and transfer encoding when streaming
|
200
198
|
def _process_options(options)
|