actionpack 7.0.8.4 → 7.1.3.4
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 +358 -362
- data/MIT-LICENSE +1 -1
- data/README.rdoc +2 -2
- data/lib/abstract_controller/base.rb +20 -11
- 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 -20
- 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 +17 -8
- 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 +139 -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 +132 -52
- 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 +9 -8
- data/lib/action_dispatch/http/filter_parameters.rb +11 -5
- 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 +38 -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 +80 -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 +9 -8
- 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 +186 -27
- data/lib/action_dispatch/middleware/executor.rb +1 -1
- 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 -15
- 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 +35 -24
- 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 +10 -15
- data/lib/action_dispatch/routing/url_for.rb +5 -1
- data/lib/action_dispatch/routing.rb +7 -7
- data/lib/action_dispatch/system_test_case.rb +3 -3
- data/lib/action_dispatch/system_testing/browser.rb +20 -19
- 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/assertion_response.rb +1 -1
- 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 +3 -3
- data/lib/action_pack/version.rb +1 -1
- data/lib/action_pack.rb +1 -1
- metadata +62 -26
@@ -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?
|
@@ -134,6 +129,7 @@ module ActionController # :nodoc:
|
|
134
129
|
#
|
135
130
|
# If you need to add verification to the beginning of the callback chain, use <tt>prepend: true</tt>.
|
136
131
|
# * <tt>:with</tt> - Set the method to handle unverified request.
|
132
|
+
# Note if <tt>default_protect_from_forgery</tt> is true, Rails call protect_from_forgery with <tt>with :exception</tt>.
|
137
133
|
#
|
138
134
|
# Built-in unverified request handling methods are:
|
139
135
|
# * <tt>:exception</tt> - Raises ActionController::InvalidAuthenticityToken exception.
|
@@ -148,18 +144,46 @@ module ActionController # :nodoc:
|
|
148
144
|
# end
|
149
145
|
#
|
150
146
|
# def handle_unverified_request
|
151
|
-
# # Custom
|
147
|
+
# # Custom behavior for unverfied request
|
152
148
|
# end
|
153
149
|
# end
|
154
150
|
#
|
155
|
-
# class ApplicationController < ActionController
|
151
|
+
# class ApplicationController < ActionController::Base
|
156
152
|
# protect_from_forgery with: CustomStrategy
|
157
153
|
# end
|
154
|
+
# * <tt>:store</tt> - Set the strategy to store and retrieve CSRF tokens.
|
155
|
+
#
|
156
|
+
# Built-in session token strategies are:
|
157
|
+
# * <tt>:session</tt> - Store the CSRF token in the session. Used as default if <tt>:store</tt> option is not specified.
|
158
|
+
# * <tt>:cookie</tt> - Store the CSRF token in an encrypted cookie.
|
159
|
+
#
|
160
|
+
# You can also implement custom strategy classes for CSRF token storage:
|
161
|
+
#
|
162
|
+
# class CustomStore
|
163
|
+
# def fetch(request)
|
164
|
+
# # Return the token from a custom location
|
165
|
+
# end
|
166
|
+
#
|
167
|
+
# def store(request, csrf_token)
|
168
|
+
# # Store the token in a custom location
|
169
|
+
# end
|
170
|
+
#
|
171
|
+
# def reset(request)
|
172
|
+
# # Delete the stored session token
|
173
|
+
# end
|
174
|
+
# end
|
175
|
+
#
|
176
|
+
# class ApplicationController < ActionController::Base
|
177
|
+
# protect_from_forgery store: CustomStore.new
|
178
|
+
# end
|
158
179
|
def protect_from_forgery(options = {})
|
159
180
|
options = options.reverse_merge(prepend: false)
|
160
181
|
|
161
182
|
self.forgery_protection_strategy = protection_method_class(options[:with] || :null_session)
|
162
183
|
self.request_forgery_protection_token ||= :authenticity_token
|
184
|
+
|
185
|
+
self.csrf_token_storage_strategy = storage_strategy(options[:store] || SessionStore.new)
|
186
|
+
|
163
187
|
before_action :verify_authenticity_token, options
|
164
188
|
append_after_action :verify_same_origin_request
|
165
189
|
end
|
@@ -188,6 +212,22 @@ module ActionController # :nodoc:
|
|
188
212
|
raise ArgumentError, "Invalid request forgery protection method, use :null_session, :exception, :reset_session, or a custom forgery protection class."
|
189
213
|
end
|
190
214
|
end
|
215
|
+
|
216
|
+
def storage_strategy(name)
|
217
|
+
case name
|
218
|
+
when :session
|
219
|
+
SessionStore.new
|
220
|
+
when :cookie
|
221
|
+
CookieStore.new(:csrf_token)
|
222
|
+
else
|
223
|
+
return name if is_storage_strategy?(name)
|
224
|
+
raise ArgumentError, "Invalid CSRF token storage strategy, use :session, :cookie, or a custom CSRF token storage class."
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def is_storage_strategy?(object)
|
229
|
+
object.respond_to?(:fetch) && object.respond_to?(:store) && object.respond_to?(:reset)
|
230
|
+
end
|
191
231
|
end
|
192
232
|
|
193
233
|
module ProtectionMethods
|
@@ -255,6 +295,68 @@ module ActionController # :nodoc:
|
|
255
295
|
end
|
256
296
|
end
|
257
297
|
|
298
|
+
class SessionStore
|
299
|
+
def fetch(request)
|
300
|
+
request.session[:_csrf_token]
|
301
|
+
end
|
302
|
+
|
303
|
+
def store(request, csrf_token)
|
304
|
+
request.session[:_csrf_token] = csrf_token
|
305
|
+
end
|
306
|
+
|
307
|
+
def reset(request)
|
308
|
+
request.session.delete(:_csrf_token)
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
class CookieStore
|
313
|
+
def initialize(cookie = :csrf_token)
|
314
|
+
@cookie_name = cookie
|
315
|
+
end
|
316
|
+
|
317
|
+
def fetch(request)
|
318
|
+
contents = request.cookie_jar.encrypted[@cookie_name]
|
319
|
+
return nil if contents.nil?
|
320
|
+
|
321
|
+
value = JSON.parse(contents)
|
322
|
+
return nil unless value.dig("session_id", "public_id") == request.session.id_was&.public_id
|
323
|
+
|
324
|
+
value["token"]
|
325
|
+
rescue JSON::ParserError
|
326
|
+
nil
|
327
|
+
end
|
328
|
+
|
329
|
+
def store(request, csrf_token)
|
330
|
+
request.cookie_jar.encrypted.permanent[@cookie_name] = {
|
331
|
+
value: {
|
332
|
+
token: csrf_token,
|
333
|
+
session_id: request.session.id,
|
334
|
+
}.to_json,
|
335
|
+
httponly: true,
|
336
|
+
same_site: :lax,
|
337
|
+
}
|
338
|
+
end
|
339
|
+
|
340
|
+
def reset(request)
|
341
|
+
request.cookie_jar.delete(@cookie_name)
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
def initialize(...)
|
346
|
+
super
|
347
|
+
@marked_for_same_origin_verification = nil
|
348
|
+
end
|
349
|
+
|
350
|
+
def reset_csrf_token(request) # :doc:
|
351
|
+
request.env.delete(CSRF_TOKEN)
|
352
|
+
csrf_token_storage_strategy.reset(request)
|
353
|
+
end
|
354
|
+
|
355
|
+
def commit_csrf_token(request) # :doc:
|
356
|
+
csrf_token = request.env[CSRF_TOKEN]
|
357
|
+
csrf_token_storage_strategy.store(request, csrf_token) unless csrf_token.nil?
|
358
|
+
end
|
359
|
+
|
258
360
|
private
|
259
361
|
# The actual before_action that is used to verify the CSRF token.
|
260
362
|
# Don't override this directly. Provide your own forgery protection
|
@@ -356,20 +458,20 @@ module ActionController # :nodoc:
|
|
356
458
|
|
357
459
|
# Creates the authenticity token for the current request.
|
358
460
|
def form_authenticity_token(form_options: {}) # :doc:
|
359
|
-
masked_authenticity_token(
|
461
|
+
masked_authenticity_token(form_options: form_options)
|
360
462
|
end
|
361
463
|
|
362
464
|
# Creates a masked version of the authenticity token that varies
|
363
465
|
# on each request. The masking is used to mitigate SSL attacks
|
364
466
|
# like BREACH.
|
365
|
-
def masked_authenticity_token(
|
467
|
+
def masked_authenticity_token(form_options: {})
|
366
468
|
action, method = form_options.values_at(:action, :method)
|
367
469
|
|
368
470
|
raw_token = if per_form_csrf_tokens && action && method
|
369
471
|
action_path = normalize_action_path(action)
|
370
|
-
per_form_csrf_token(
|
472
|
+
per_form_csrf_token(nil, action_path, method)
|
371
473
|
else
|
372
|
-
global_csrf_token
|
474
|
+
global_csrf_token
|
373
475
|
end
|
374
476
|
|
375
477
|
mask_token(raw_token)
|
@@ -397,14 +499,14 @@ module ActionController # :nodoc:
|
|
397
499
|
# This is actually an unmasked token. This is expected if
|
398
500
|
# you have just upgraded to masked tokens, but should stop
|
399
501
|
# happening shortly after installing this gem.
|
400
|
-
compare_with_real_token masked_token
|
502
|
+
compare_with_real_token masked_token
|
401
503
|
|
402
504
|
elsif masked_token.length == AUTHENTICITY_TOKEN_LENGTH * 2
|
403
505
|
csrf_token = unmask_token(masked_token)
|
404
506
|
|
405
|
-
compare_with_global_token(csrf_token
|
406
|
-
compare_with_real_token(csrf_token
|
407
|
-
valid_per_form_csrf_token?(csrf_token
|
507
|
+
compare_with_global_token(csrf_token) ||
|
508
|
+
compare_with_real_token(csrf_token) ||
|
509
|
+
valid_per_form_csrf_token?(csrf_token)
|
408
510
|
else
|
409
511
|
false # Token is malformed.
|
410
512
|
end
|
@@ -425,15 +527,15 @@ module ActionController # :nodoc:
|
|
425
527
|
encode_csrf_token(masked_token)
|
426
528
|
end
|
427
529
|
|
428
|
-
def compare_with_real_token(token, session) # :doc:
|
530
|
+
def compare_with_real_token(token, session = nil) # :doc:
|
429
531
|
ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, real_csrf_token(session))
|
430
532
|
end
|
431
533
|
|
432
|
-
def compare_with_global_token(token, session) # :doc:
|
534
|
+
def compare_with_global_token(token, session = nil) # :doc:
|
433
535
|
ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, global_csrf_token(session))
|
434
536
|
end
|
435
537
|
|
436
|
-
def valid_per_form_csrf_token?(token, session) # :doc:
|
538
|
+
def valid_per_form_csrf_token?(token, session = nil) # :doc:
|
437
539
|
if per_form_csrf_tokens
|
438
540
|
correct_token = per_form_csrf_token(
|
439
541
|
session,
|
@@ -447,9 +549,12 @@ module ActionController # :nodoc:
|
|
447
549
|
end
|
448
550
|
end
|
449
551
|
|
450
|
-
def real_csrf_token(
|
451
|
-
|
452
|
-
|
552
|
+
def real_csrf_token(_session = nil) # :doc:
|
553
|
+
csrf_token = request.env.fetch(CSRF_TOKEN) do
|
554
|
+
request.env[CSRF_TOKEN] = csrf_token_storage_strategy.fetch(request) || generate_csrf_token
|
555
|
+
end
|
556
|
+
|
557
|
+
decode_csrf_token(csrf_token)
|
453
558
|
end
|
454
559
|
|
455
560
|
def per_form_csrf_token(session, action_path, method) # :doc:
|
@@ -459,7 +564,7 @@ module ActionController # :nodoc:
|
|
459
564
|
GLOBAL_CSRF_TOKEN_IDENTIFIER = "!real_csrf_token"
|
460
565
|
private_constant :GLOBAL_CSRF_TOKEN_IDENTIFIER
|
461
566
|
|
462
|
-
def global_csrf_token(session) # :doc:
|
567
|
+
def global_csrf_token(session = nil) # :doc:
|
463
568
|
csrf_token_hmac(session, GLOBAL_CSRF_TOKEN_IDENTIFIER)
|
464
569
|
end
|
465
570
|
|
@@ -519,31 +624,15 @@ module ActionController # :nodoc:
|
|
519
624
|
end
|
520
625
|
|
521
626
|
def generate_csrf_token # :nodoc:
|
522
|
-
|
523
|
-
SecureRandom.urlsafe_base64(AUTHENTICITY_TOKEN_LENGTH)
|
524
|
-
else
|
525
|
-
SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
|
526
|
-
end
|
627
|
+
SecureRandom.urlsafe_base64(AUTHENTICITY_TOKEN_LENGTH)
|
527
628
|
end
|
528
629
|
|
529
630
|
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
|
631
|
+
Base64.urlsafe_encode64(csrf_token, padding: false)
|
535
632
|
end
|
536
633
|
|
537
634
|
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
|
635
|
+
Base64.urlsafe_decode64(encoded_csrf_token)
|
547
636
|
end
|
548
637
|
end
|
549
638
|
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
|