actionpack 7.0.4.3 → 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.

Files changed (140) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +380 -284
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -4
  5. data/lib/abstract_controller/base.rb +20 -11
  6. data/lib/abstract_controller/caching/fragments.rb +2 -0
  7. data/lib/abstract_controller/callbacks.rb +31 -6
  8. data/lib/abstract_controller/deprecator.rb +7 -0
  9. data/lib/abstract_controller/helpers.rb +75 -28
  10. data/lib/abstract_controller/railties/routes_helpers.rb +1 -16
  11. data/lib/abstract_controller/rendering.rb +12 -14
  12. data/lib/abstract_controller/translation.rb +9 -6
  13. data/lib/abstract_controller/url_for.rb +2 -0
  14. data/lib/abstract_controller.rb +6 -0
  15. data/lib/action_controller/api.rb +6 -4
  16. data/lib/action_controller/base.rb +3 -17
  17. data/lib/action_controller/caching.rb +2 -0
  18. data/lib/action_controller/deprecator.rb +7 -0
  19. data/lib/action_controller/form_builder.rb +2 -0
  20. data/lib/action_controller/log_subscriber.rb +16 -4
  21. data/lib/action_controller/metal/basic_implicit_render.rb +3 -1
  22. data/lib/action_controller/metal/conditional_get.rb +121 -123
  23. data/lib/action_controller/metal/content_security_policy.rb +5 -5
  24. data/lib/action_controller/metal/data_streaming.rb +20 -18
  25. data/lib/action_controller/metal/default_headers.rb +2 -0
  26. data/lib/action_controller/metal/etag_with_flash.rb +3 -1
  27. data/lib/action_controller/metal/etag_with_template_digest.rb +2 -0
  28. data/lib/action_controller/metal/exceptions.rb +8 -0
  29. data/lib/action_controller/metal/head.rb +9 -7
  30. data/lib/action_controller/metal/helpers.rb +3 -14
  31. data/lib/action_controller/metal/http_authentication.rb +17 -8
  32. data/lib/action_controller/metal/implicit_render.rb +5 -3
  33. data/lib/action_controller/metal/instrumentation.rb +8 -1
  34. data/lib/action_controller/metal/live.rb +25 -1
  35. data/lib/action_controller/metal/mime_responds.rb +2 -2
  36. data/lib/action_controller/metal/params_wrapper.rb +4 -2
  37. data/lib/action_controller/metal/permissions_policy.rb +2 -2
  38. data/lib/action_controller/metal/redirecting.rb +25 -8
  39. data/lib/action_controller/metal/renderers.rb +4 -4
  40. data/lib/action_controller/metal/rendering.rb +114 -9
  41. data/lib/action_controller/metal/request_forgery_protection.rb +144 -53
  42. data/lib/action_controller/metal/rescue.rb +6 -3
  43. data/lib/action_controller/metal/streaming.rb +71 -31
  44. data/lib/action_controller/metal/strong_parameters.rb +158 -101
  45. data/lib/action_controller/metal/url_for.rb +9 -4
  46. data/lib/action_controller/metal.rb +79 -21
  47. data/lib/action_controller/railtie.rb +24 -10
  48. data/lib/action_controller/renderer.rb +99 -85
  49. data/lib/action_controller/test_case.rb +15 -5
  50. data/lib/action_controller.rb +8 -1
  51. data/lib/action_dispatch/constants.rb +32 -0
  52. data/lib/action_dispatch/deprecator.rb +7 -0
  53. data/lib/action_dispatch/http/cache.rb +8 -10
  54. data/lib/action_dispatch/http/content_security_policy.rb +14 -9
  55. data/lib/action_dispatch/http/filter_parameters.rb +14 -28
  56. data/lib/action_dispatch/http/headers.rb +3 -1
  57. data/lib/action_dispatch/http/mime_negotiation.rb +22 -22
  58. data/lib/action_dispatch/http/mime_type.rb +35 -12
  59. data/lib/action_dispatch/http/mime_types.rb +3 -1
  60. data/lib/action_dispatch/http/parameters.rb +1 -1
  61. data/lib/action_dispatch/http/permissions_policy.rb +38 -23
  62. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  63. data/lib/action_dispatch/http/request.rb +63 -30
  64. data/lib/action_dispatch/http/response.rb +80 -63
  65. data/lib/action_dispatch/http/upload.rb +15 -2
  66. data/lib/action_dispatch/journey/formatter.rb +8 -2
  67. data/lib/action_dispatch/journey/path/pattern.rb +14 -14
  68. data/lib/action_dispatch/journey/route.rb +3 -2
  69. data/lib/action_dispatch/journey/router.rb +9 -8
  70. data/lib/action_dispatch/journey/routes.rb +2 -2
  71. data/lib/action_dispatch/log_subscriber.rb +23 -0
  72. data/lib/action_dispatch/middleware/actionable_exceptions.rb +5 -6
  73. data/lib/action_dispatch/middleware/assume_ssl.rb +24 -0
  74. data/lib/action_dispatch/middleware/callbacks.rb +2 -0
  75. data/lib/action_dispatch/middleware/cookies.rb +85 -102
  76. data/lib/action_dispatch/middleware/debug_exceptions.rb +26 -25
  77. data/lib/action_dispatch/middleware/debug_locks.rb +4 -1
  78. data/lib/action_dispatch/middleware/debug_view.rb +7 -2
  79. data/lib/action_dispatch/middleware/exception_wrapper.rb +186 -27
  80. data/lib/action_dispatch/middleware/executor.rb +1 -1
  81. data/lib/action_dispatch/middleware/flash.rb +7 -0
  82. data/lib/action_dispatch/middleware/host_authorization.rb +18 -8
  83. data/lib/action_dispatch/middleware/public_exceptions.rb +5 -3
  84. data/lib/action_dispatch/middleware/reloader.rb +7 -5
  85. data/lib/action_dispatch/middleware/remote_ip.rb +21 -20
  86. data/lib/action_dispatch/middleware/request_id.rb +4 -2
  87. data/lib/action_dispatch/middleware/server_timing.rb +4 -4
  88. data/lib/action_dispatch/middleware/session/abstract_store.rb +5 -0
  89. data/lib/action_dispatch/middleware/session/cache_store.rb +2 -0
  90. data/lib/action_dispatch/middleware/session/cookie_store.rb +11 -5
  91. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +3 -1
  92. data/lib/action_dispatch/middleware/show_exceptions.rb +25 -18
  93. data/lib/action_dispatch/middleware/ssl.rb +18 -6
  94. data/lib/action_dispatch/middleware/stack.rb +7 -2
  95. data/lib/action_dispatch/middleware/static.rb +14 -10
  96. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
  97. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +4 -4
  98. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
  99. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -3
  100. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -3
  101. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +7 -7
  102. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
  103. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +17 -0
  104. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +16 -12
  105. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -1
  106. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
  107. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +4 -4
  108. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  109. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  110. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +3 -0
  111. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +59 -41
  112. data/lib/action_dispatch/railtie.rb +14 -4
  113. data/lib/action_dispatch/request/session.rb +16 -6
  114. data/lib/action_dispatch/request/utils.rb +8 -3
  115. data/lib/action_dispatch/routing/inspector.rb +54 -6
  116. data/lib/action_dispatch/routing/mapper.rb +58 -24
  117. data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -0
  118. data/lib/action_dispatch/routing/redirection.rb +15 -6
  119. data/lib/action_dispatch/routing/route_set.rb +52 -22
  120. data/lib/action_dispatch/routing/routes_proxy.rb +10 -15
  121. data/lib/action_dispatch/routing/url_for.rb +26 -22
  122. data/lib/action_dispatch/routing.rb +7 -7
  123. data/lib/action_dispatch/system_test_case.rb +3 -3
  124. data/lib/action_dispatch/system_testing/browser.rb +20 -19
  125. data/lib/action_dispatch/system_testing/driver.rb +14 -22
  126. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +27 -16
  127. data/lib/action_dispatch/testing/assertion_response.rb +1 -1
  128. data/lib/action_dispatch/testing/assertions/response.rb +14 -7
  129. data/lib/action_dispatch/testing/assertions/routing.rb +67 -28
  130. data/lib/action_dispatch/testing/assertions.rb +3 -1
  131. data/lib/action_dispatch/testing/integration.rb +27 -17
  132. data/lib/action_dispatch/testing/request_encoder.rb +4 -1
  133. data/lib/action_dispatch/testing/test_process.rb +4 -3
  134. data/lib/action_dispatch/testing/test_request.rb +1 -1
  135. data/lib/action_dispatch/testing/test_response.rb +23 -9
  136. data/lib/action_dispatch.rb +37 -4
  137. data/lib/action_pack/gem_version.rb +4 -4
  138. data/lib/action_pack/version.rb +1 -1
  139. data/lib/action_pack.rb +1 -1
  140. metadata +65 -29
@@ -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
- # <tt>ActionController::InvalidAuthenticityToken</tt> error on unverified requests.
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
- # Controls whether URL-safe CSRF tokens are generated.
94
- config_accessor :urlsafe_csrf_tokens, instance_writer: false
95
- self.urlsafe_csrf_tokens = true
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?
@@ -118,9 +113,11 @@ module ActionController # :nodoc:
118
113
  # protect_from_forgery except: :index
119
114
  # end
120
115
  #
121
- # You can disable forgery protection on controller by skipping the verification before_action:
116
+ # You can disable forgery protection on a controller using skip_forgery_protection:
122
117
  #
123
- # skip_before_action :verify_authenticity_token
118
+ # class BarController < ApplicationController
119
+ # skip_forgery_protection
120
+ # end
124
121
  #
125
122
  # Valid Options:
126
123
  #
@@ -132,6 +129,7 @@ module ActionController # :nodoc:
132
129
  #
133
130
  # If you need to add verification to the beginning of the callback chain, use <tt>prepend: true</tt>.
134
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>.
135
133
  #
136
134
  # Built-in unverified request handling methods are:
137
135
  # * <tt>:exception</tt> - Raises ActionController::InvalidAuthenticityToken exception.
@@ -146,18 +144,46 @@ module ActionController # :nodoc:
146
144
  # end
147
145
  #
148
146
  # def handle_unverified_request
149
- # # Custom behaviour for unverfied request
147
+ # # Custom behavior for unverfied request
150
148
  # end
151
149
  # end
152
150
  #
153
- # class ApplicationController < ActionController:x:Base
151
+ # class ApplicationController < ActionController::Base
154
152
  # protect_from_forgery with: CustomStrategy
155
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
156
179
  def protect_from_forgery(options = {})
157
180
  options = options.reverse_merge(prepend: false)
158
181
 
159
182
  self.forgery_protection_strategy = protection_method_class(options[:with] || :null_session)
160
183
  self.request_forgery_protection_token ||= :authenticity_token
184
+
185
+ self.csrf_token_storage_strategy = storage_strategy(options[:store] || SessionStore.new)
186
+
161
187
  before_action :verify_authenticity_token, options
162
188
  append_after_action :verify_same_origin_request
163
189
  end
@@ -186,6 +212,22 @@ module ActionController # :nodoc:
186
212
  raise ArgumentError, "Invalid request forgery protection method, use :null_session, :exception, :reset_session, or a custom forgery protection class."
187
213
  end
188
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
189
231
  end
190
232
 
191
233
  module ProtectionMethods
@@ -253,6 +295,68 @@ module ActionController # :nodoc:
253
295
  end
254
296
  end
255
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
+
256
360
  private
257
361
  # The actual before_action that is used to verify the CSRF token.
258
362
  # Don't override this directly. Provide your own forgery protection
@@ -334,7 +438,7 @@ module ActionController # :nodoc:
334
438
  #
335
439
  # * Is it a GET or HEAD request? GETs should be safe and idempotent
336
440
  # * Does the form_authenticity_token match the given token value from the params?
337
- # * Does the X-CSRF-Token header match the form_authenticity_token?
441
+ # * Does the +X-CSRF-Token+ header match the form_authenticity_token?
338
442
  def verified_request? # :doc:
339
443
  !protect_against_forgery? || request.get? || request.head? ||
340
444
  (valid_request_origin? && any_authenticity_token_valid?)
@@ -354,20 +458,20 @@ module ActionController # :nodoc:
354
458
 
355
459
  # Creates the authenticity token for the current request.
356
460
  def form_authenticity_token(form_options: {}) # :doc:
357
- masked_authenticity_token(session, form_options: form_options)
461
+ masked_authenticity_token(form_options: form_options)
358
462
  end
359
463
 
360
464
  # Creates a masked version of the authenticity token that varies
361
465
  # on each request. The masking is used to mitigate SSL attacks
362
466
  # like BREACH.
363
- def masked_authenticity_token(session, form_options: {})
467
+ def masked_authenticity_token(form_options: {})
364
468
  action, method = form_options.values_at(:action, :method)
365
469
 
366
470
  raw_token = if per_form_csrf_tokens && action && method
367
471
  action_path = normalize_action_path(action)
368
- per_form_csrf_token(session, action_path, method)
472
+ per_form_csrf_token(nil, action_path, method)
369
473
  else
370
- global_csrf_token(session)
474
+ global_csrf_token
371
475
  end
372
476
 
373
477
  mask_token(raw_token)
@@ -395,14 +499,14 @@ module ActionController # :nodoc:
395
499
  # This is actually an unmasked token. This is expected if
396
500
  # you have just upgraded to masked tokens, but should stop
397
501
  # happening shortly after installing this gem.
398
- compare_with_real_token masked_token, session
502
+ compare_with_real_token masked_token
399
503
 
400
504
  elsif masked_token.length == AUTHENTICITY_TOKEN_LENGTH * 2
401
505
  csrf_token = unmask_token(masked_token)
402
506
 
403
- compare_with_global_token(csrf_token, session) ||
404
- compare_with_real_token(csrf_token, session) ||
405
- valid_per_form_csrf_token?(csrf_token, session)
507
+ compare_with_global_token(csrf_token) ||
508
+ compare_with_real_token(csrf_token) ||
509
+ valid_per_form_csrf_token?(csrf_token)
406
510
  else
407
511
  false # Token is malformed.
408
512
  end
@@ -423,15 +527,15 @@ module ActionController # :nodoc:
423
527
  encode_csrf_token(masked_token)
424
528
  end
425
529
 
426
- def compare_with_real_token(token, session) # :doc:
530
+ def compare_with_real_token(token, session = nil) # :doc:
427
531
  ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, real_csrf_token(session))
428
532
  end
429
533
 
430
- def compare_with_global_token(token, session) # :doc:
534
+ def compare_with_global_token(token, session = nil) # :doc:
431
535
  ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, global_csrf_token(session))
432
536
  end
433
537
 
434
- def valid_per_form_csrf_token?(token, session) # :doc:
538
+ def valid_per_form_csrf_token?(token, session = nil) # :doc:
435
539
  if per_form_csrf_tokens
436
540
  correct_token = per_form_csrf_token(
437
541
  session,
@@ -445,9 +549,12 @@ module ActionController # :nodoc:
445
549
  end
446
550
  end
447
551
 
448
- def real_csrf_token(session) # :doc:
449
- session[:_csrf_token] ||= generate_csrf_token
450
- decode_csrf_token(session[:_csrf_token])
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)
451
558
  end
452
559
 
453
560
  def per_form_csrf_token(session, action_path, method) # :doc:
@@ -457,7 +564,7 @@ module ActionController # :nodoc:
457
564
  GLOBAL_CSRF_TOKEN_IDENTIFIER = "!real_csrf_token"
458
565
  private_constant :GLOBAL_CSRF_TOKEN_IDENTIFIER
459
566
 
460
- def global_csrf_token(session) # :doc:
567
+ def global_csrf_token(session = nil) # :doc:
461
568
  csrf_token_hmac(session, GLOBAL_CSRF_TOKEN_IDENTIFIER)
462
569
  end
463
570
 
@@ -517,31 +624,15 @@ module ActionController # :nodoc:
517
624
  end
518
625
 
519
626
  def generate_csrf_token # :nodoc:
520
- if urlsafe_csrf_tokens
521
- SecureRandom.urlsafe_base64(AUTHENTICITY_TOKEN_LENGTH)
522
- else
523
- SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
524
- end
627
+ SecureRandom.urlsafe_base64(AUTHENTICITY_TOKEN_LENGTH)
525
628
  end
526
629
 
527
630
  def encode_csrf_token(csrf_token) # :nodoc:
528
- if urlsafe_csrf_tokens
529
- Base64.urlsafe_encode64(csrf_token, padding: false)
530
- else
531
- Base64.strict_encode64(csrf_token)
532
- end
631
+ Base64.urlsafe_encode64(csrf_token, padding: false)
533
632
  end
534
633
 
535
634
  def decode_csrf_token(encoded_csrf_token) # :nodoc:
536
- if urlsafe_csrf_tokens
537
- Base64.urlsafe_decode64(encoded_csrf_token)
538
- else
539
- begin
540
- Base64.strict_decode64(encoded_csrf_token)
541
- rescue ArgumentError
542
- Base64.urlsafe_decode64(encoded_csrf_token)
543
- end
544
- end
635
+ Base64.urlsafe_decode64(encoded_csrf_token)
545
636
  end
546
637
  end
547
638
  end
@@ -1,9 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionController # :nodoc:
4
- # This module is responsible for providing +rescue_from+ helpers
5
- # to controllers and configuring when detailed exceptions must be
6
- # shown.
4
+ # = Action Controller \Rescue
5
+ #
6
+ # This module is responsible for providing
7
+ # {rescue_from}[rdoc-ref:ActiveSupport::Rescuable::ClassMethods#rescue_from]
8
+ # to controllers, wrapping actions to handle configured errors, and
9
+ # configuring when detailed exceptions must be shown.
7
10
  module Rescue
8
11
  extend ActiveSupport::Concern
9
12
  include ActiveSupport::Rescuable
@@ -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
- # streaming each part of the layout as they are processed. This allows the
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, allowing JavaScripts and stylesheets to be loaded
15
+ # to client very quickly, enabling JavaScripts and stylesheets to be loaded
16
16
  # earlier than usual.
17
17
  #
18
- # This approach was introduced in Rails 3.1 and is still improving. Several
19
- # Rack middlewares may not work and you need to be careful when streaming.
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 kills the purpose of streaming. For this purpose, you can use
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
- # Giving:
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,11 +140,11 @@ 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, <tt>Rack::Bug</tt> won't work when streaming as it
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 <tt>Rack::Cache</tt> won't work with streaming as it does not support
150
- # streaming bodies yet. Whenever streaming Cache-Control is automatically
146
+ # Also +Rack::Cache+ won't work with streaming as it does not support
147
+ # streaming bodies yet. Whenever streaming +Cache-Control+ is automatically
151
148
  # set to "no-cache".
152
149
  #
153
150
  # == Errors
@@ -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 happens
165
- # while rendering attributes for a given tag. You can check the real cause
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
- # Please check its documentation for more information: https://bogomips.org/unicorn/Unicorn/Configurator.html#method-i-listen
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
- # To be described.
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
- Rack::Chunked::Body.new view_renderer.render_body(view_context, options)
254
+ Body.new view_renderer.render_body(view_context, options)
215
255
  else
216
256
  super
217
257
  end