actionpack 7.0.10 → 7.1.0.beta1
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 +318 -452
- data/MIT-LICENSE +1 -1
- data/README.rdoc +2 -2
- data/lib/abstract_controller/base.rb +19 -10
- data/lib/abstract_controller/caching/fragments.rb +2 -0
- data/lib/abstract_controller/callbacks.rb +31 -6
- data/lib/abstract_controller/deprecator.rb +7 -0
- data/lib/abstract_controller/helpers.rb +61 -18
- data/lib/abstract_controller/railties/routes_helpers.rb +1 -16
- data/lib/abstract_controller/rendering.rb +3 -3
- data/lib/abstract_controller/translation.rb +1 -27
- data/lib/abstract_controller/url_for.rb +2 -0
- data/lib/abstract_controller.rb +6 -0
- data/lib/action_controller/api.rb +5 -3
- data/lib/action_controller/base.rb +3 -17
- data/lib/action_controller/caching.rb +2 -0
- data/lib/action_controller/deprecator.rb +7 -0
- data/lib/action_controller/form_builder.rb +2 -0
- data/lib/action_controller/log_subscriber.rb +16 -4
- data/lib/action_controller/metal/content_security_policy.rb +1 -1
- data/lib/action_controller/metal/data_streaming.rb +2 -0
- data/lib/action_controller/metal/default_headers.rb +2 -0
- data/lib/action_controller/metal/etag_with_flash.rb +2 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +2 -0
- data/lib/action_controller/metal/exceptions.rb +8 -0
- data/lib/action_controller/metal/head.rb +8 -6
- data/lib/action_controller/metal/helpers.rb +3 -14
- data/lib/action_controller/metal/http_authentication.rb +11 -5
- data/lib/action_controller/metal/implicit_render.rb +5 -3
- data/lib/action_controller/metal/instrumentation.rb +8 -1
- data/lib/action_controller/metal/live.rb +24 -0
- data/lib/action_controller/metal/mime_responds.rb +2 -2
- data/lib/action_controller/metal/params_wrapper.rb +4 -2
- data/lib/action_controller/metal/permissions_policy.rb +1 -1
- data/lib/action_controller/metal/redirecting.rb +7 -7
- data/lib/action_controller/metal/renderers.rb +2 -2
- data/lib/action_controller/metal/rendering.rb +0 -7
- data/lib/action_controller/metal/request_forgery_protection.rb +138 -50
- data/lib/action_controller/metal/rescue.rb +2 -0
- data/lib/action_controller/metal/streaming.rb +70 -30
- data/lib/action_controller/metal/strong_parameters.rb +89 -50
- data/lib/action_controller/metal/url_for.rb +7 -0
- data/lib/action_controller/metal.rb +79 -21
- data/lib/action_controller/railtie.rb +22 -9
- data/lib/action_controller/renderer.rb +98 -65
- data/lib/action_controller/test_case.rb +15 -5
- data/lib/action_controller.rb +8 -1
- data/lib/action_dispatch/constants.rb +32 -0
- data/lib/action_dispatch/deprecator.rb +7 -0
- data/lib/action_dispatch/http/cache.rb +1 -3
- data/lib/action_dispatch/http/content_security_policy.rb +13 -29
- data/lib/action_dispatch/http/filter_parameters.rb +15 -14
- data/lib/action_dispatch/http/headers.rb +2 -0
- data/lib/action_dispatch/http/mime_negotiation.rb +22 -22
- data/lib/action_dispatch/http/mime_type.rb +35 -12
- data/lib/action_dispatch/http/mime_types.rb +3 -1
- data/lib/action_dispatch/http/parameters.rb +1 -1
- data/lib/action_dispatch/http/permissions_policy.rb +45 -16
- data/lib/action_dispatch/http/rack_cache.rb +2 -0
- data/lib/action_dispatch/http/request.rb +48 -14
- data/lib/action_dispatch/http/response.rb +78 -59
- data/lib/action_dispatch/http/upload.rb +2 -0
- data/lib/action_dispatch/journey/formatter.rb +8 -2
- data/lib/action_dispatch/journey/path/pattern.rb +14 -14
- data/lib/action_dispatch/journey/route.rb +3 -2
- data/lib/action_dispatch/journey/router.rb +5 -4
- data/lib/action_dispatch/journey/routes.rb +2 -2
- data/lib/action_dispatch/log_subscriber.rb +23 -0
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +5 -6
- 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 +81 -98
- data/lib/action_dispatch/middleware/debug_exceptions.rb +26 -25
- data/lib/action_dispatch/middleware/debug_locks.rb +4 -1
- data/lib/action_dispatch/middleware/debug_view.rb +7 -2
- data/lib/action_dispatch/middleware/exception_wrapper.rb +181 -27
- data/lib/action_dispatch/middleware/executor.rb +1 -7
- data/lib/action_dispatch/middleware/flash.rb +7 -0
- data/lib/action_dispatch/middleware/host_authorization.rb +6 -3
- 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 +17 -16
- data/lib/action_dispatch/middleware/request_id.rb +2 -0
- data/lib/action_dispatch/middleware/server_timing.rb +4 -4
- data/lib/action_dispatch/middleware/session/abstract_store.rb +5 -0
- data/lib/action_dispatch/middleware/session/cache_store.rb +2 -0
- data/lib/action_dispatch/middleware/session/cookie_store.rb +11 -5
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +3 -1
- data/lib/action_dispatch/middleware/show_exceptions.rb +19 -16
- data/lib/action_dispatch/middleware/ssl.rb +18 -6
- data/lib/action_dispatch/middleware/stack.rb +7 -2
- data/lib/action_dispatch/middleware/static.rb +12 -8
- data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +7 -7
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +17 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +16 -12
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
- 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 +46 -37
- data/lib/action_dispatch/railtie.rb +14 -4
- data/lib/action_dispatch/request/session.rb +16 -6
- data/lib/action_dispatch/request/utils.rb +8 -3
- data/lib/action_dispatch/routing/inspector.rb +54 -6
- data/lib/action_dispatch/routing/mapper.rb +26 -14
- data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -0
- data/lib/action_dispatch/routing/redirection.rb +15 -6
- data/lib/action_dispatch/routing/route_set.rb +52 -22
- data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
- data/lib/action_dispatch/routing/url_for.rb +5 -1
- data/lib/action_dispatch/routing.rb +4 -4
- data/lib/action_dispatch/system_test_case.rb +3 -3
- data/lib/action_dispatch/system_testing/browser.rb +5 -6
- data/lib/action_dispatch/system_testing/driver.rb +13 -21
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +27 -16
- data/lib/action_dispatch/testing/assertions/response.rb +13 -6
- data/lib/action_dispatch/testing/assertions/routing.rb +67 -28
- data/lib/action_dispatch/testing/assertions.rb +3 -1
- data/lib/action_dispatch/testing/integration.rb +27 -17
- data/lib/action_dispatch/testing/request_encoder.rb +4 -1
- data/lib/action_dispatch/testing/test_process.rb +4 -3
- data/lib/action_dispatch/testing/test_request.rb +1 -1
- data/lib/action_dispatch/testing/test_response.rb +23 -9
- data/lib/action_dispatch.rb +37 -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 +44 -33
|
@@ -11,6 +11,8 @@ module ActionController # :nodoc:
|
|
|
11
11
|
class InvalidCrossOriginRequest < ActionControllerError # :nodoc:
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
+
# = Action Controller Request Forgery Protection
|
|
15
|
+
#
|
|
14
16
|
# Controller actions are protected from Cross-Site Request Forgery (CSRF) attacks
|
|
15
17
|
# by including a token in the rendered HTML for your application. This token is
|
|
16
18
|
# stored as a random string in the session, to which an attacker does not have
|
|
@@ -34,10 +36,10 @@ module ActionController # :nodoc:
|
|
|
34
36
|
#
|
|
35
37
|
# Subclasses of ActionController::Base are protected by default with the
|
|
36
38
|
# <tt>:exception</tt> strategy, which raises an
|
|
37
|
-
#
|
|
39
|
+
# ActionController::InvalidAuthenticityToken error on unverified requests.
|
|
38
40
|
#
|
|
39
41
|
# APIs may want to disable this behavior since they are typically designed to be
|
|
40
|
-
# state-less: that is, the request API client handles the session instead of Rails.
|
|
42
|
+
# state-less: that is, the request API client handles the session instead of \Rails.
|
|
41
43
|
# One way to achieve this is to use the <tt>:null_session</tt> strategy instead,
|
|
42
44
|
# which allows unverified requests to be handled, but with an empty session:
|
|
43
45
|
#
|
|
@@ -55,6 +57,8 @@ module ActionController # :nodoc:
|
|
|
55
57
|
# Learn more about CSRF attacks and securing your application in the
|
|
56
58
|
# {Ruby on Rails Security Guide}[https://guides.rubyonrails.org/security.html].
|
|
57
59
|
module RequestForgeryProtection
|
|
60
|
+
CSRF_TOKEN = "action_controller.csrf_token"
|
|
61
|
+
|
|
58
62
|
extend ActiveSupport::Concern
|
|
59
63
|
|
|
60
64
|
include AbstractController::Helpers
|
|
@@ -90,18 +94,9 @@ module ActionController # :nodoc:
|
|
|
90
94
|
config_accessor :default_protect_from_forgery
|
|
91
95
|
self.default_protect_from_forgery = false
|
|
92
96
|
|
|
93
|
-
#
|
|
94
|
-
config_accessor :
|
|
95
|
-
self.
|
|
96
|
-
|
|
97
|
-
singleton_class.redefine_method(:urlsafe_csrf_tokens=) do |urlsafe_csrf_tokens|
|
|
98
|
-
if urlsafe_csrf_tokens
|
|
99
|
-
ActiveSupport::Deprecation.warn("URL-safe CSRF tokens are now the default. Use 6.1 defaults or above.")
|
|
100
|
-
else
|
|
101
|
-
ActiveSupport::Deprecation.warn("Non-URL-safe CSRF tokens are deprecated. Use 6.1 defaults or above.")
|
|
102
|
-
end
|
|
103
|
-
config.urlsafe_csrf_tokens = urlsafe_csrf_tokens
|
|
104
|
-
end
|
|
97
|
+
# The strategy to use for storing and retrieving CSRF tokens.
|
|
98
|
+
config_accessor :csrf_token_storage_strategy
|
|
99
|
+
self.csrf_token_storage_strategy = SessionStore.new
|
|
105
100
|
|
|
106
101
|
helper_method :form_authenticity_token
|
|
107
102
|
helper_method :protect_against_forgery?
|
|
@@ -148,18 +143,46 @@ module ActionController # :nodoc:
|
|
|
148
143
|
# end
|
|
149
144
|
#
|
|
150
145
|
# def handle_unverified_request
|
|
151
|
-
# # Custom
|
|
146
|
+
# # Custom behavior for unverfied request
|
|
152
147
|
# end
|
|
153
148
|
# end
|
|
154
149
|
#
|
|
155
|
-
# class ApplicationController < ActionController
|
|
150
|
+
# class ApplicationController < ActionController::Base
|
|
156
151
|
# protect_from_forgery with: CustomStrategy
|
|
157
152
|
# end
|
|
153
|
+
# * <tt>:store</tt> - Set the strategy to store and retrieve CSRF tokens.
|
|
154
|
+
#
|
|
155
|
+
# Built-in session token strategies are:
|
|
156
|
+
# * <tt>:session</tt> - Store the CSRF token in the session. Used as default if <tt>:store</tt> option is not specified.
|
|
157
|
+
# * <tt>:cookie</tt> - Store the CSRF token in an encrypted cookie.
|
|
158
|
+
#
|
|
159
|
+
# You can also implement custom strategy classes for CSRF token storage:
|
|
160
|
+
#
|
|
161
|
+
# class CustomStore
|
|
162
|
+
# def fetch(request)
|
|
163
|
+
# # Return the token from a custom location
|
|
164
|
+
# end
|
|
165
|
+
#
|
|
166
|
+
# def store(request, csrf_token)
|
|
167
|
+
# # Store the token in a custom location
|
|
168
|
+
# end
|
|
169
|
+
#
|
|
170
|
+
# def reset(request)
|
|
171
|
+
# # Delete the stored session token
|
|
172
|
+
# end
|
|
173
|
+
# end
|
|
174
|
+
#
|
|
175
|
+
# class ApplicationController < ActionController::Base
|
|
176
|
+
# protect_from_forgery store: CustomStore.new
|
|
177
|
+
# end
|
|
158
178
|
def protect_from_forgery(options = {})
|
|
159
179
|
options = options.reverse_merge(prepend: false)
|
|
160
180
|
|
|
161
181
|
self.forgery_protection_strategy = protection_method_class(options[:with] || :null_session)
|
|
162
182
|
self.request_forgery_protection_token ||= :authenticity_token
|
|
183
|
+
|
|
184
|
+
self.csrf_token_storage_strategy = storage_strategy(options[:store] || SessionStore.new)
|
|
185
|
+
|
|
163
186
|
before_action :verify_authenticity_token, options
|
|
164
187
|
append_after_action :verify_same_origin_request
|
|
165
188
|
end
|
|
@@ -188,6 +211,22 @@ module ActionController # :nodoc:
|
|
|
188
211
|
raise ArgumentError, "Invalid request forgery protection method, use :null_session, :exception, :reset_session, or a custom forgery protection class."
|
|
189
212
|
end
|
|
190
213
|
end
|
|
214
|
+
|
|
215
|
+
def storage_strategy(name)
|
|
216
|
+
case name
|
|
217
|
+
when :session
|
|
218
|
+
SessionStore.new
|
|
219
|
+
when :cookie
|
|
220
|
+
CookieStore.new(:csrf_token)
|
|
221
|
+
else
|
|
222
|
+
return name if is_storage_strategy?(name)
|
|
223
|
+
raise ArgumentError, "Invalid CSRF token storage strategy, use :session, :cookie, or a custom CSRF token storage class."
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def is_storage_strategy?(object)
|
|
228
|
+
object.respond_to?(:fetch) && object.respond_to?(:store) && object.respond_to?(:reset)
|
|
229
|
+
end
|
|
191
230
|
end
|
|
192
231
|
|
|
193
232
|
module ProtectionMethods
|
|
@@ -255,6 +294,68 @@ module ActionController # :nodoc:
|
|
|
255
294
|
end
|
|
256
295
|
end
|
|
257
296
|
|
|
297
|
+
class SessionStore
|
|
298
|
+
def fetch(request)
|
|
299
|
+
request.session[:_csrf_token]
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def store(request, csrf_token)
|
|
303
|
+
request.session[:_csrf_token] = csrf_token
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def reset(request)
|
|
307
|
+
request.session.delete(:_csrf_token)
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
class CookieStore
|
|
312
|
+
def initialize(cookie = :csrf_token)
|
|
313
|
+
@cookie_name = cookie
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def fetch(request)
|
|
317
|
+
contents = request.cookie_jar.encrypted[@cookie_name]
|
|
318
|
+
return nil if contents.nil?
|
|
319
|
+
|
|
320
|
+
value = JSON.parse(contents)
|
|
321
|
+
return nil unless value.dig("session_id", "public_id") == request.session.id_was&.public_id
|
|
322
|
+
|
|
323
|
+
value["token"]
|
|
324
|
+
rescue JSON::ParserError
|
|
325
|
+
nil
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def store(request, csrf_token)
|
|
329
|
+
request.cookie_jar.encrypted.permanent[@cookie_name] = {
|
|
330
|
+
value: {
|
|
331
|
+
token: csrf_token,
|
|
332
|
+
session_id: request.session.id,
|
|
333
|
+
}.to_json,
|
|
334
|
+
httponly: true,
|
|
335
|
+
same_site: :lax,
|
|
336
|
+
}
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def reset(request)
|
|
340
|
+
request.cookie_jar.delete(@cookie_name)
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def initialize(...)
|
|
345
|
+
super
|
|
346
|
+
@marked_for_same_origin_verification = nil
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def reset_csrf_token(request) # :doc:
|
|
350
|
+
request.env.delete(CSRF_TOKEN)
|
|
351
|
+
csrf_token_storage_strategy.reset(request)
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def commit_csrf_token(request) # :doc:
|
|
355
|
+
csrf_token = request.env[CSRF_TOKEN]
|
|
356
|
+
csrf_token_storage_strategy.store(request, csrf_token) unless csrf_token.nil?
|
|
357
|
+
end
|
|
358
|
+
|
|
258
359
|
private
|
|
259
360
|
# The actual before_action that is used to verify the CSRF token.
|
|
260
361
|
# Don't override this directly. Provide your own forgery protection
|
|
@@ -356,20 +457,20 @@ module ActionController # :nodoc:
|
|
|
356
457
|
|
|
357
458
|
# Creates the authenticity token for the current request.
|
|
358
459
|
def form_authenticity_token(form_options: {}) # :doc:
|
|
359
|
-
masked_authenticity_token(
|
|
460
|
+
masked_authenticity_token(form_options: form_options)
|
|
360
461
|
end
|
|
361
462
|
|
|
362
463
|
# Creates a masked version of the authenticity token that varies
|
|
363
464
|
# on each request. The masking is used to mitigate SSL attacks
|
|
364
465
|
# like BREACH.
|
|
365
|
-
def masked_authenticity_token(
|
|
466
|
+
def masked_authenticity_token(form_options: {})
|
|
366
467
|
action, method = form_options.values_at(:action, :method)
|
|
367
468
|
|
|
368
469
|
raw_token = if per_form_csrf_tokens && action && method
|
|
369
470
|
action_path = normalize_action_path(action)
|
|
370
|
-
per_form_csrf_token(
|
|
471
|
+
per_form_csrf_token(nil, action_path, method)
|
|
371
472
|
else
|
|
372
|
-
global_csrf_token
|
|
473
|
+
global_csrf_token
|
|
373
474
|
end
|
|
374
475
|
|
|
375
476
|
mask_token(raw_token)
|
|
@@ -397,14 +498,14 @@ module ActionController # :nodoc:
|
|
|
397
498
|
# This is actually an unmasked token. This is expected if
|
|
398
499
|
# you have just upgraded to masked tokens, but should stop
|
|
399
500
|
# happening shortly after installing this gem.
|
|
400
|
-
compare_with_real_token masked_token
|
|
501
|
+
compare_with_real_token masked_token
|
|
401
502
|
|
|
402
503
|
elsif masked_token.length == AUTHENTICITY_TOKEN_LENGTH * 2
|
|
403
504
|
csrf_token = unmask_token(masked_token)
|
|
404
505
|
|
|
405
|
-
compare_with_global_token(csrf_token
|
|
406
|
-
compare_with_real_token(csrf_token
|
|
407
|
-
valid_per_form_csrf_token?(csrf_token
|
|
506
|
+
compare_with_global_token(csrf_token) ||
|
|
507
|
+
compare_with_real_token(csrf_token) ||
|
|
508
|
+
valid_per_form_csrf_token?(csrf_token)
|
|
408
509
|
else
|
|
409
510
|
false # Token is malformed.
|
|
410
511
|
end
|
|
@@ -425,15 +526,15 @@ module ActionController # :nodoc:
|
|
|
425
526
|
encode_csrf_token(masked_token)
|
|
426
527
|
end
|
|
427
528
|
|
|
428
|
-
def compare_with_real_token(token, session) # :doc:
|
|
529
|
+
def compare_with_real_token(token, session = nil) # :doc:
|
|
429
530
|
ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, real_csrf_token(session))
|
|
430
531
|
end
|
|
431
532
|
|
|
432
|
-
def compare_with_global_token(token, session) # :doc:
|
|
533
|
+
def compare_with_global_token(token, session = nil) # :doc:
|
|
433
534
|
ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, global_csrf_token(session))
|
|
434
535
|
end
|
|
435
536
|
|
|
436
|
-
def valid_per_form_csrf_token?(token, session) # :doc:
|
|
537
|
+
def valid_per_form_csrf_token?(token, session = nil) # :doc:
|
|
437
538
|
if per_form_csrf_tokens
|
|
438
539
|
correct_token = per_form_csrf_token(
|
|
439
540
|
session,
|
|
@@ -447,9 +548,12 @@ module ActionController # :nodoc:
|
|
|
447
548
|
end
|
|
448
549
|
end
|
|
449
550
|
|
|
450
|
-
def real_csrf_token(
|
|
451
|
-
|
|
452
|
-
|
|
551
|
+
def real_csrf_token(_session = nil) # :doc:
|
|
552
|
+
csrf_token = request.env.fetch(CSRF_TOKEN) do
|
|
553
|
+
request.env[CSRF_TOKEN] = csrf_token_storage_strategy.fetch(request) || generate_csrf_token
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
decode_csrf_token(csrf_token)
|
|
453
557
|
end
|
|
454
558
|
|
|
455
559
|
def per_form_csrf_token(session, action_path, method) # :doc:
|
|
@@ -459,7 +563,7 @@ module ActionController # :nodoc:
|
|
|
459
563
|
GLOBAL_CSRF_TOKEN_IDENTIFIER = "!real_csrf_token"
|
|
460
564
|
private_constant :GLOBAL_CSRF_TOKEN_IDENTIFIER
|
|
461
565
|
|
|
462
|
-
def global_csrf_token(session) # :doc:
|
|
566
|
+
def global_csrf_token(session = nil) # :doc:
|
|
463
567
|
csrf_token_hmac(session, GLOBAL_CSRF_TOKEN_IDENTIFIER)
|
|
464
568
|
end
|
|
465
569
|
|
|
@@ -519,31 +623,15 @@ module ActionController # :nodoc:
|
|
|
519
623
|
end
|
|
520
624
|
|
|
521
625
|
def generate_csrf_token # :nodoc:
|
|
522
|
-
|
|
523
|
-
SecureRandom.urlsafe_base64(AUTHENTICITY_TOKEN_LENGTH)
|
|
524
|
-
else
|
|
525
|
-
SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
|
|
526
|
-
end
|
|
626
|
+
SecureRandom.urlsafe_base64(AUTHENTICITY_TOKEN_LENGTH)
|
|
527
627
|
end
|
|
528
628
|
|
|
529
629
|
def encode_csrf_token(csrf_token) # :nodoc:
|
|
530
|
-
|
|
531
|
-
Base64.urlsafe_encode64(csrf_token, padding: false)
|
|
532
|
-
else
|
|
533
|
-
Base64.strict_encode64(csrf_token)
|
|
534
|
-
end
|
|
630
|
+
Base64.urlsafe_encode64(csrf_token, padding: false)
|
|
535
631
|
end
|
|
536
632
|
|
|
537
633
|
def decode_csrf_token(encoded_csrf_token) # :nodoc:
|
|
538
|
-
|
|
539
|
-
Base64.urlsafe_decode64(encoded_csrf_token)
|
|
540
|
-
else
|
|
541
|
-
begin
|
|
542
|
-
Base64.strict_decode64(encoded_csrf_token)
|
|
543
|
-
rescue ArgumentError
|
|
544
|
-
Base64.urlsafe_decode64(encoded_csrf_token)
|
|
545
|
-
end
|
|
546
|
-
end
|
|
634
|
+
Base64.urlsafe_decode64(encoded_csrf_token)
|
|
547
635
|
end
|
|
548
636
|
end
|
|
549
637
|
end
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module ActionController # :nodoc:
|
|
4
|
+
# = Action Controller \Rescue
|
|
5
|
+
#
|
|
4
6
|
# This module is responsible for providing
|
|
5
7
|
# {rescue_from}[rdoc-ref:ActiveSupport::Rescuable::ClassMethods#rescue_from]
|
|
6
8
|
# to controllers, wrapping actions to handle configured errors, and
|
|
@@ -1,30 +1,25 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "rack/chunked"
|
|
4
|
-
|
|
5
3
|
module ActionController # :nodoc:
|
|
4
|
+
# = Action Controller \Streaming
|
|
5
|
+
#
|
|
6
6
|
# Allows views to be streamed back to the client as they are rendered.
|
|
7
7
|
#
|
|
8
|
-
# By default, Rails renders views by first rendering the template
|
|
8
|
+
# By default, \Rails renders views by first rendering the template
|
|
9
9
|
# and then the layout. The response is sent to the client after the whole
|
|
10
10
|
# template is rendered, all queries are made, and the layout is processed.
|
|
11
11
|
#
|
|
12
|
-
# Streaming inverts the rendering flow by rendering the layout first and
|
|
13
|
-
#
|
|
12
|
+
# \Streaming inverts the rendering flow by rendering the layout first and
|
|
13
|
+
# subsequently each part of the layout as they are processed. This allows the
|
|
14
14
|
# header of the HTML (which is usually in the layout) to be streamed back
|
|
15
|
-
# to client very quickly,
|
|
15
|
+
# to client very quickly, enabling JavaScripts and stylesheets to be loaded
|
|
16
16
|
# earlier than usual.
|
|
17
17
|
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
# Those points are going to be addressed soon.
|
|
21
|
-
#
|
|
22
|
-
# In order to use streaming, you will need to use a Ruby version that
|
|
23
|
-
# supports fibers (fibers are supported since version 1.9.2 of the main
|
|
24
|
-
# Ruby implementation).
|
|
18
|
+
# Several Rack middlewares may not work and you need to be careful when streaming.
|
|
19
|
+
# This is covered in more detail below, see the Streaming@Middlewares section.
|
|
25
20
|
#
|
|
26
|
-
# Streaming can be added to a given template easily, all you need to do is
|
|
27
|
-
# to pass the +:stream+ option
|
|
21
|
+
# \Streaming can be added to a given template easily, all you need to do is
|
|
22
|
+
# to pass the +:stream+ option to +render+.
|
|
28
23
|
#
|
|
29
24
|
# class PostsController
|
|
30
25
|
# def index
|
|
@@ -35,7 +30,7 @@ module ActionController # :nodoc:
|
|
|
35
30
|
#
|
|
36
31
|
# == When to use streaming
|
|
37
32
|
#
|
|
38
|
-
# Streaming may be considered to be overkill for lightweight actions like
|
|
33
|
+
# \Streaming may be considered to be overkill for lightweight actions like
|
|
39
34
|
# +new+ or +edit+. The real benefit of streaming is on expensive actions
|
|
40
35
|
# that, for example, do a lot of queries on the database.
|
|
41
36
|
#
|
|
@@ -59,13 +54,13 @@ module ActionController # :nodoc:
|
|
|
59
54
|
# render stream: true
|
|
60
55
|
# end
|
|
61
56
|
#
|
|
62
|
-
# Notice that +:stream+ only works with templates. Rendering +:json+
|
|
57
|
+
# Notice that +:stream+ only works with templates. \Rendering +:json+
|
|
63
58
|
# or +:xml+ with +:stream+ won't work.
|
|
64
59
|
#
|
|
65
60
|
# == Communication between layout and template
|
|
66
61
|
#
|
|
67
62
|
# When streaming, rendering happens top-down instead of inside-out.
|
|
68
|
-
# Rails starts with the layout, and the template is rendered later,
|
|
63
|
+
# \Rails starts with the layout, and the template is rendered later,
|
|
69
64
|
# when its +yield+ is reached.
|
|
70
65
|
#
|
|
71
66
|
# This means that, if your application currently relies on instance
|
|
@@ -112,7 +107,7 @@ module ActionController # :nodoc:
|
|
|
112
107
|
# This means that, if you have <code>yield :title</code> in your layout
|
|
113
108
|
# and you want to use streaming, you would have to render the whole template
|
|
114
109
|
# (and eventually trigger all queries) before streaming the title and all
|
|
115
|
-
# assets, which
|
|
110
|
+
# assets, which defeats the purpose of streaming. Alternatively, you can use
|
|
116
111
|
# a helper called +provide+ that does the same as +content_for+ but tells the
|
|
117
112
|
# layout to stop searching for other entries and continue rendering.
|
|
118
113
|
#
|
|
@@ -122,7 +117,7 @@ module ActionController # :nodoc:
|
|
|
122
117
|
# Hello
|
|
123
118
|
# <%= content_for :title, " page" %>
|
|
124
119
|
#
|
|
125
|
-
#
|
|
120
|
+
# Resulting in:
|
|
126
121
|
#
|
|
127
122
|
# <html>
|
|
128
123
|
# <head><title>Main</title></head>
|
|
@@ -132,6 +127,8 @@ module ActionController # :nodoc:
|
|
|
132
127
|
# That said, when streaming, you need to properly check your templates
|
|
133
128
|
# and choose when to use +provide+ and +content_for+.
|
|
134
129
|
#
|
|
130
|
+
# See also ActionView::Helpers::CaptureHelper for more information.
|
|
131
|
+
#
|
|
135
132
|
# == Headers, cookies, session, and flash
|
|
136
133
|
#
|
|
137
134
|
# When streaming, the HTTP headers are sent to the client right before
|
|
@@ -143,10 +140,10 @@ module ActionController # :nodoc:
|
|
|
143
140
|
#
|
|
144
141
|
# Middlewares that need to manipulate the body won't work with streaming.
|
|
145
142
|
# You should disable those middlewares whenever streaming in development
|
|
146
|
-
# or production. For instance,
|
|
143
|
+
# or production. For instance, +Rack::Bug+ won't work when streaming as it
|
|
147
144
|
# needs to inject contents in the HTML body.
|
|
148
145
|
#
|
|
149
|
-
# Also
|
|
146
|
+
# Also +Rack::Cache+ won't work with streaming as it does not support
|
|
150
147
|
# streaming bodies yet. Whenever streaming +Cache-Control+ is automatically
|
|
151
148
|
# set to "no-cache".
|
|
152
149
|
#
|
|
@@ -156,14 +153,14 @@ module ActionController # :nodoc:
|
|
|
156
153
|
# happens because part of the template was already rendered and streamed to
|
|
157
154
|
# the client, making it impossible to render a whole exception page.
|
|
158
155
|
#
|
|
159
|
-
# Currently, when an exception happens in development or production, Rails
|
|
156
|
+
# Currently, when an exception happens in development or production, \Rails
|
|
160
157
|
# will automatically stream to the client:
|
|
161
158
|
#
|
|
162
159
|
# "><script>window.location = "/500.html"</script></html>
|
|
163
160
|
#
|
|
164
|
-
# The first two characters (">) are required in case the exception
|
|
165
|
-
# while rendering attributes for a given tag. You can check the real
|
|
166
|
-
# for the exception in your logger.
|
|
161
|
+
# The first two characters (<tt>"></tt>) are required in case the exception
|
|
162
|
+
# happens while rendering attributes for a given tag. You can check the real
|
|
163
|
+
# cause for the exception in your logger.
|
|
167
164
|
#
|
|
168
165
|
# == Web server support
|
|
169
166
|
#
|
|
@@ -183,16 +180,59 @@ module ActionController # :nodoc:
|
|
|
183
180
|
# unicorn_rails --config-file unicorn.config.rb
|
|
184
181
|
#
|
|
185
182
|
# You may also want to configure other parameters like <tt>:tcp_nodelay</tt>.
|
|
186
|
-
#
|
|
183
|
+
#
|
|
184
|
+
# For more information, please check the
|
|
185
|
+
# {documentation}[https://bogomips.org/unicorn/Unicorn/Configurator.html#method-i-listen].
|
|
187
186
|
#
|
|
188
187
|
# If you are using Unicorn with NGINX, you may need to tweak NGINX.
|
|
189
|
-
# Streaming should work out of the box on Rainbows.
|
|
188
|
+
# \Streaming should work out of the box on Rainbows.
|
|
190
189
|
#
|
|
191
190
|
# ==== Passenger
|
|
192
191
|
#
|
|
193
|
-
#
|
|
192
|
+
# Phusion Passenger with NGINX, offers two streaming mechanisms out of the box.
|
|
193
|
+
#
|
|
194
|
+
# 1. NGINX response buffering mechanism which is dependent on the value of
|
|
195
|
+
# +passenger_buffer_response+ option (default is "off").
|
|
196
|
+
# 2. Passenger buffering system which is always 'on' irrespective of the value
|
|
197
|
+
# of +passenger_buffer_response+.
|
|
198
|
+
#
|
|
199
|
+
# When +passenger_buffer_response+ is turned "on", then streaming would be
|
|
200
|
+
# done at the NGINX level which waits until the application is done sending
|
|
201
|
+
# the response back to the client.
|
|
202
|
+
#
|
|
203
|
+
# For more information, please check the
|
|
204
|
+
# {documentation}[https://www.phusionpassenger.com/docs/references/config_reference/nginx/#passenger_buffer_response].
|
|
194
205
|
#
|
|
195
206
|
module Streaming
|
|
207
|
+
class Body # :nodoc:
|
|
208
|
+
TERM = "\r\n"
|
|
209
|
+
TAIL = "0#{TERM}"
|
|
210
|
+
|
|
211
|
+
# Store the response body to be chunked.
|
|
212
|
+
def initialize(body)
|
|
213
|
+
@body = body
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# For each element yielded by the response body, yield
|
|
217
|
+
# the element in chunked encoding.
|
|
218
|
+
def each(&block)
|
|
219
|
+
term = TERM
|
|
220
|
+
@body.each do |chunk|
|
|
221
|
+
size = chunk.bytesize
|
|
222
|
+
next if size == 0
|
|
223
|
+
|
|
224
|
+
yield [size.to_s(16), term, chunk.b, term].join
|
|
225
|
+
end
|
|
226
|
+
yield TAIL
|
|
227
|
+
yield term
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Close the response body if the response body supports it.
|
|
231
|
+
def close
|
|
232
|
+
@body.close if @body.respond_to?(:close)
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
196
236
|
private
|
|
197
237
|
# Set proper cache control and transfer encoding when streaming
|
|
198
238
|
def _process_options(options)
|
|
@@ -211,7 +251,7 @@ module ActionController # :nodoc:
|
|
|
211
251
|
# Call render_body if we are streaming instead of usual +render+.
|
|
212
252
|
def _render_template(options)
|
|
213
253
|
if options.delete(:stream)
|
|
214
|
-
|
|
254
|
+
Body.new view_renderer.render_body(view_context, options)
|
|
215
255
|
else
|
|
216
256
|
super
|
|
217
257
|
end
|