actionpack 6.1.7 → 7.0.4.1

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 (121) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +269 -406
  3. data/MIT-LICENSE +1 -0
  4. data/README.rdoc +2 -3
  5. data/lib/abstract_controller/asset_paths.rb +1 -1
  6. data/lib/abstract_controller/base.rb +13 -26
  7. data/lib/abstract_controller/caching/fragments.rb +2 -2
  8. data/lib/abstract_controller/caching.rb +1 -1
  9. data/lib/abstract_controller/callbacks.rb +21 -7
  10. data/lib/abstract_controller/collector.rb +2 -2
  11. data/lib/abstract_controller/error.rb +1 -1
  12. data/lib/abstract_controller/helpers.rb +4 -3
  13. data/lib/abstract_controller/logger.rb +1 -1
  14. data/lib/abstract_controller/railties/routes_helpers.rb +2 -0
  15. data/lib/abstract_controller/translation.rb +3 -2
  16. data/lib/abstract_controller/url_for.rb +4 -6
  17. data/lib/action_controller/api.rb +6 -6
  18. data/lib/action_controller/base.rb +5 -4
  19. data/lib/action_controller/form_builder.rb +2 -2
  20. data/lib/action_controller/log_subscriber.rb +4 -3
  21. data/lib/action_controller/metal/conditional_get.rb +39 -2
  22. data/lib/action_controller/metal/content_security_policy.rb +36 -2
  23. data/lib/action_controller/metal/cookies.rb +1 -1
  24. data/lib/action_controller/metal/data_streaming.rb +5 -13
  25. data/lib/action_controller/metal/exceptions.rb +19 -30
  26. data/lib/action_controller/metal/flash.rb +6 -2
  27. data/lib/action_controller/metal/helpers.rb +2 -2
  28. data/lib/action_controller/metal/http_authentication.rb +66 -39
  29. data/lib/action_controller/metal/instrumentation.rb +57 -52
  30. data/lib/action_controller/metal/live.rb +43 -2
  31. data/lib/action_controller/metal/mime_responds.rb +3 -3
  32. data/lib/action_controller/metal/params_wrapper.rb +20 -11
  33. data/lib/action_controller/metal/permissions_policy.rb +19 -28
  34. data/lib/action_controller/metal/redirecting.rb +93 -18
  35. data/lib/action_controller/metal/renderers.rb +10 -11
  36. data/lib/action_controller/metal/rendering.rb +8 -8
  37. data/lib/action_controller/metal/request_forgery_protection.rb +78 -29
  38. data/lib/action_controller/metal/rescue.rb +1 -1
  39. data/lib/action_controller/metal/streaming.rb +6 -8
  40. data/lib/action_controller/metal/strong_parameters.rb +100 -54
  41. data/lib/action_controller/metal/testing.rb +9 -2
  42. data/lib/action_controller/metal/url_for.rb +3 -3
  43. data/lib/action_controller/metal.rb +10 -13
  44. data/lib/action_controller/railtie.rb +49 -6
  45. data/lib/action_controller/renderer.rb +1 -1
  46. data/lib/action_controller/test_case.rb +28 -7
  47. data/lib/action_controller.rb +2 -5
  48. data/lib/action_dispatch/http/cache.rb +14 -7
  49. data/lib/action_dispatch/http/content_security_policy.rb +108 -35
  50. data/lib/action_dispatch/http/filter_parameters.rb +5 -0
  51. data/lib/action_dispatch/http/mime_negotiation.rb +15 -5
  52. data/lib/action_dispatch/http/mime_type.rb +9 -11
  53. data/lib/action_dispatch/http/parameters.rb +5 -5
  54. data/lib/action_dispatch/http/permissions_policy.rb +17 -1
  55. data/lib/action_dispatch/http/request.rb +12 -21
  56. data/lib/action_dispatch/http/response.rb +3 -16
  57. data/lib/action_dispatch/http/url.rb +11 -19
  58. data/lib/action_dispatch/journey/gtg/builder.rb +11 -12
  59. data/lib/action_dispatch/journey/gtg/simulator.rb +10 -4
  60. data/lib/action_dispatch/journey/gtg/transition_table.rb +77 -21
  61. data/lib/action_dispatch/journey/nodes/node.rb +70 -5
  62. data/lib/action_dispatch/journey/path/pattern.rb +22 -13
  63. data/lib/action_dispatch/journey/route.rb +6 -13
  64. data/lib/action_dispatch/journey/router/utils.rb +2 -2
  65. data/lib/action_dispatch/journey/router.rb +1 -1
  66. data/lib/action_dispatch/journey/routes.rb +3 -3
  67. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  68. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  69. data/lib/action_dispatch/middleware/actionable_exceptions.rb +0 -1
  70. data/lib/action_dispatch/middleware/cookies.rb +42 -27
  71. data/lib/action_dispatch/middleware/debug_exceptions.rb +6 -4
  72. data/lib/action_dispatch/middleware/debug_locks.rb +3 -3
  73. data/lib/action_dispatch/middleware/exception_wrapper.rb +4 -0
  74. data/lib/action_dispatch/middleware/executor.rb +3 -0
  75. data/lib/action_dispatch/middleware/flash.rb +17 -18
  76. data/lib/action_dispatch/middleware/host_authorization.rb +1 -12
  77. data/lib/action_dispatch/middleware/remote_ip.rb +16 -4
  78. data/lib/action_dispatch/middleware/request_id.rb +1 -1
  79. data/lib/action_dispatch/middleware/server_timing.rb +76 -0
  80. data/lib/action_dispatch/middleware/session/abstract_store.rb +1 -1
  81. data/lib/action_dispatch/middleware/session/cookie_store.rb +9 -9
  82. data/lib/action_dispatch/middleware/show_exceptions.rb +7 -9
  83. data/lib/action_dispatch/middleware/stack.rb +27 -9
  84. data/lib/action_dispatch/middleware/static.rb +2 -6
  85. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +1 -1
  86. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +4 -11
  87. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +2 -2
  88. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +3 -2
  89. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +2 -0
  90. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +4 -4
  91. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +3 -3
  92. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +28 -18
  93. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +3 -3
  94. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +3 -3
  95. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
  96. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +3 -3
  97. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +3 -3
  98. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +5 -14
  99. data/lib/action_dispatch/railtie.rb +8 -2
  100. data/lib/action_dispatch/request/session.rb +43 -13
  101. data/lib/action_dispatch/routing/inspector.rb +1 -1
  102. data/lib/action_dispatch/routing/mapper.rb +59 -83
  103. data/lib/action_dispatch/routing/redirection.rb +5 -2
  104. data/lib/action_dispatch/routing/route_set.rb +17 -7
  105. data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
  106. data/lib/action_dispatch/routing/url_for.rb +4 -5
  107. data/lib/action_dispatch/routing.rb +5 -6
  108. data/lib/action_dispatch/system_test_case.rb +5 -5
  109. data/lib/action_dispatch/system_testing/browser.rb +2 -12
  110. data/lib/action_dispatch/system_testing/driver.rb +35 -11
  111. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +11 -7
  112. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +0 -8
  113. data/lib/action_dispatch/testing/assertions/routing.rb +3 -2
  114. data/lib/action_dispatch/testing/assertions.rb +2 -5
  115. data/lib/action_dispatch/testing/integration.rb +6 -8
  116. data/lib/action_dispatch/testing/test_process.rb +3 -29
  117. data/lib/action_dispatch/testing/test_response.rb +20 -2
  118. data/lib/action_dispatch.rb +1 -0
  119. data/lib/action_pack/gem_version.rb +5 -5
  120. data/lib/action_pack/version.rb +1 -1
  121. metadata +16 -15
@@ -1,20 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionController
4
- class ActionControllerError < StandardError #:nodoc:
4
+ class ActionControllerError < StandardError # :nodoc:
5
5
  end
6
6
 
7
- class BadRequest < ActionControllerError #:nodoc:
7
+ class BadRequest < ActionControllerError # :nodoc:
8
8
  def initialize(msg = nil)
9
9
  super(msg)
10
10
  set_backtrace $!.backtrace if $!
11
11
  end
12
12
  end
13
13
 
14
- class RenderError < ActionControllerError #:nodoc:
14
+ class RenderError < ActionControllerError # :nodoc:
15
15
  end
16
16
 
17
- class RoutingError < ActionControllerError #:nodoc:
17
+ class RoutingError < ActionControllerError # :nodoc:
18
18
  attr_reader :failures
19
19
  def initialize(message, failures = [])
20
20
  super(message)
@@ -22,7 +22,7 @@ module ActionController
22
22
  end
23
23
  end
24
24
 
25
- class UrlGenerationError < ActionControllerError #:nodoc:
25
+ class UrlGenerationError < ActionControllerError # :nodoc:
26
26
  attr_reader :routes, :route_name, :method_name
27
27
 
28
28
  def initialize(message, routes = nil, route_name = nil, method_name = nil)
@@ -33,44 +33,33 @@ module ActionController
33
33
  super(message)
34
34
  end
35
35
 
36
- class Correction
37
- def initialize(error)
38
- @error = error
39
- end
36
+ if defined?(DidYouMean::Correctable) && defined?(DidYouMean::SpellChecker)
37
+ include DidYouMean::Correctable
40
38
 
41
39
  def corrections
42
- if @error.method_name
43
- maybe_these = @error.routes.named_routes.helper_names.grep(/#{@error.route_name}/)
44
- maybe_these -= [@error.method_name.to_s] # remove exact match
45
-
46
- maybe_these.sort_by { |n|
47
- DidYouMean::Jaro.distance(@error.route_name, n)
48
- }.reverse.first(4)
49
- else
50
- []
40
+ @corrections ||= begin
41
+ maybe_these = routes&.named_routes&.helper_names&.grep(/#{route_name}/) || []
42
+ maybe_these -= [method_name.to_s] # remove exact match
43
+
44
+ DidYouMean::SpellChecker.new(dictionary: maybe_these).correct(route_name)
51
45
  end
52
46
  end
53
47
  end
54
-
55
- # We may not have DYM, and DYM might not let us register error handlers
56
- if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
57
- DidYouMean.correct_error(self, Correction)
58
- end
59
48
  end
60
49
 
61
- class MethodNotAllowed < ActionControllerError #:nodoc:
50
+ class MethodNotAllowed < ActionControllerError # :nodoc:
62
51
  def initialize(*allowed_methods)
63
52
  super("Only #{allowed_methods.to_sentence} requests are allowed.")
64
53
  end
65
54
  end
66
55
 
67
- class NotImplemented < MethodNotAllowed #:nodoc:
56
+ class NotImplemented < MethodNotAllowed # :nodoc:
68
57
  end
69
58
 
70
- class MissingFile < ActionControllerError #:nodoc:
59
+ class MissingFile < ActionControllerError # :nodoc:
71
60
  end
72
61
 
73
- class SessionOverflowError < ActionControllerError #:nodoc:
62
+ class SessionOverflowError < ActionControllerError # :nodoc:
74
63
  DEFAULT_MESSAGE = "Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data."
75
64
 
76
65
  def initialize(message = nil)
@@ -78,10 +67,10 @@ module ActionController
78
67
  end
79
68
  end
80
69
 
81
- class UnknownHttpMethod < ActionControllerError #:nodoc:
70
+ class UnknownHttpMethod < ActionControllerError # :nodoc:
82
71
  end
83
72
 
84
- class UnknownFormat < ActionControllerError #:nodoc:
73
+ class UnknownFormat < ActionControllerError # :nodoc:
85
74
  end
86
75
 
87
76
  # Raised when a nested respond_to is triggered and the content types of each
@@ -102,6 +91,6 @@ module ActionController
102
91
  end
103
92
  end
104
93
 
105
- class MissingExactTemplate < UnknownFormat #:nodoc:
94
+ class MissingExactTemplate < UnknownFormat # :nodoc:
106
95
  end
107
96
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ActionController #:nodoc:
3
+ module ActionController # :nodoc:
4
4
  module Flash
5
5
  extend ActiveSupport::Concern
6
6
 
@@ -41,10 +41,14 @@ module ActionController #:nodoc:
41
41
  self._flash_types += [type]
42
42
  end
43
43
  end
44
+
45
+ def action_methods # :nodoc:
46
+ @action_methods ||= super - _flash_types.map(&:to_s).to_set
47
+ end
44
48
  end
45
49
 
46
50
  private
47
- def redirect_to(options = {}, response_options_and_flash = {}) #:doc:
51
+ def redirect_to(options = {}, response_options_and_flash = {}) # :doc:
48
52
  self.class._flash_types.each do |flash_type|
49
53
  if type = response_options_and_flash.delete(flash_type)
50
54
  flash[flash_type] = type
@@ -26,7 +26,7 @@ module ActionController
26
26
  #
27
27
  # module FormattedTimeHelper
28
28
  # def format_time(time, format=:long, blank_message="&nbsp;")
29
- # time.blank? ? blank_message : time.to_s(format)
29
+ # time.blank? ? blank_message : time.to_fs(format)
30
30
  # end
31
31
  # end
32
32
  #
@@ -91,7 +91,7 @@ module ActionController
91
91
  end
92
92
  end
93
93
 
94
- # Overwrite modules_for_helpers to accept :all as argument, which loads
94
+ # Override modules_for_helpers to accept +:all+ as argument, which loads
95
95
  # all helpers in helpers_path.
96
96
  #
97
97
  # ==== Parameters
@@ -5,9 +5,9 @@ require "active_support/security_utils"
5
5
  require "active_support/core_ext/array/access"
6
6
 
7
7
  module ActionController
8
- # Makes it dead easy to do HTTP Basic, Digest and Token authentication.
8
+ # HTTP Basic, Digest, and Token authentication.
9
9
  module HttpAuthentication
10
- # Makes it dead easy to do HTTP \Basic authentication.
10
+ # = HTTP \Basic authentication
11
11
  #
12
12
  # === Simple \Basic example
13
13
  #
@@ -25,8 +25,8 @@ module ActionController
25
25
  #
26
26
  # === Advanced \Basic example
27
27
  #
28
- # Here is a more advanced \Basic example where only Atom feeds and the XML API is protected by HTTP authentication,
29
- # the regular HTML interface is protected by a session approach:
28
+ # Here is a more advanced \Basic example where only Atom feeds and the XML API are protected by HTTP authentication.
29
+ # The regular HTML interface is protected by a session approach:
30
30
  #
31
31
  # class ApplicationController < ActionController::Base
32
32
  # before_action :set_account, :authenticate
@@ -70,7 +70,12 @@ module ActionController
70
70
  extend ActiveSupport::Concern
71
71
 
72
72
  module ClassMethods
73
+ # Enables HTTP \Basic authentication.
74
+ #
75
+ # See ActionController::HttpAuthentication::Basic for example usage.
73
76
  def http_basic_authenticate_with(name:, password:, realm: nil, **options)
77
+ raise ArgumentError, "Expected name: to be a String, got #{name.class}" unless name.is_a?(String)
78
+ raise ArgumentError, "Expected password: to be a String, got #{password.class}" unless password.is_a?(String)
74
79
  before_action(options) { http_basic_authenticate_or_request_with name: name, password: password, realm: realm }
75
80
  end
76
81
  end
@@ -79,8 +84,8 @@ module ActionController
79
84
  authenticate_or_request_with_http_basic(realm, message) do |given_name, given_password|
80
85
  # This comparison uses & so that it doesn't short circuit and
81
86
  # uses `secure_compare` so that length information isn't leaked.
82
- ActiveSupport::SecurityUtils.secure_compare(given_name, name) &
83
- ActiveSupport::SecurityUtils.secure_compare(given_password, password)
87
+ ActiveSupport::SecurityUtils.secure_compare(given_name.to_s, name) &
88
+ ActiveSupport::SecurityUtils.secure_compare(given_password.to_s, password)
84
89
  end
85
90
  end
86
91
 
@@ -135,15 +140,15 @@ module ActionController
135
140
  end
136
141
  end
137
142
 
138
- # Makes it dead easy to do HTTP \Digest authentication.
143
+ # = HTTP \Digest authentication
139
144
  #
140
145
  # === Simple \Digest example
141
146
  #
142
- # require "digest/md5"
147
+ # require "openssl"
143
148
  # class PostsController < ApplicationController
144
149
  # REALM = "SuperSecret"
145
150
  # USERS = {"dhh" => "secret", #plain text password
146
- # "dap" => Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password
151
+ # "dap" => OpenSSL::Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password
147
152
  #
148
153
  # before_action :authenticate, except: [:index]
149
154
  #
@@ -181,22 +186,28 @@ module ActionController
181
186
  extend self
182
187
 
183
188
  module ControllerMethods
189
+ # Authenticate using an HTTP \Digest, or otherwise render an HTTP header
190
+ # requesting the client to send a \Digest.
191
+ #
192
+ # See ActionController::HttpAuthentication::Digest for example usage.
184
193
  def authenticate_or_request_with_http_digest(realm = "Application", message = nil, &password_procedure)
185
194
  authenticate_with_http_digest(realm, &password_procedure) || request_http_digest_authentication(realm, message)
186
195
  end
187
196
 
188
- # Authenticate with HTTP Digest, returns true or false
197
+ # Authenticate using an HTTP \Digest. Returns true if authentication is
198
+ # successful, false otherwise.
189
199
  def authenticate_with_http_digest(realm = "Application", &password_procedure)
190
200
  HttpAuthentication::Digest.authenticate(request, realm, &password_procedure)
191
201
  end
192
202
 
193
- # Render output including the HTTP Digest authentication header
203
+ # Render an HTTP header requesting the client to send a \Digest for
204
+ # authentication.
194
205
  def request_http_digest_authentication(realm = "Application", message = nil)
195
206
  HttpAuthentication::Digest.authentication_request(self, realm, message)
196
207
  end
197
208
  end
198
209
 
199
- # Returns false on a valid response, true otherwise
210
+ # Returns false on a valid response, true otherwise.
200
211
  def authenticate(request, realm, &password_procedure)
201
212
  request.authorization && validate_digest_response(request, realm, &password_procedure)
202
213
  end
@@ -231,12 +242,12 @@ module ActionController
231
242
  # of a plain-text password.
232
243
  def expected_response(http_method, uri, credentials, password, password_is_ha1 = true)
233
244
  ha1 = password_is_ha1 ? password : ha1(credentials, password)
234
- ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(":"))
235
- ::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(":"))
245
+ ha2 = OpenSSL::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(":"))
246
+ OpenSSL::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(":"))
236
247
  end
237
248
 
238
249
  def ha1(credentials, password)
239
- ::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(":"))
250
+ OpenSSL::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(":"))
240
251
  end
241
252
 
242
253
  def encode_credentials(http_method, credentials, password, password_is_ha1)
@@ -301,7 +312,7 @@ module ActionController
301
312
  #
302
313
  # An implementation might choose not to accept a previously used nonce or a previously used digest, in order to
303
314
  # protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for
304
- # POST, PUT, or PATCH requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
315
+ # POST, PUT, or PATCH requests, and a time-stamp for GET requests. For more details on the issues involved see Section 4
305
316
  # of this document.
306
317
  #
307
318
  # The nonce is opaque to the client. Composed of Time, and hash of Time with secret
@@ -310,7 +321,7 @@ module ActionController
310
321
  def nonce(secret_key, time = Time.now)
311
322
  t = time.to_i
312
323
  hashed = [t, secret_key]
313
- digest = ::Digest::MD5.hexdigest(hashed.join(":"))
324
+ digest = OpenSSL::Digest::MD5.hexdigest(hashed.join(":"))
314
325
  ::Base64.strict_encode64("#{t}:#{digest}")
315
326
  end
316
327
 
@@ -327,13 +338,13 @@ module ActionController
327
338
 
328
339
  # Opaque based on digest of secret key
329
340
  def opaque(secret_key)
330
- ::Digest::MD5.hexdigest(secret_key)
341
+ OpenSSL::Digest::MD5.hexdigest(secret_key)
331
342
  end
332
343
  end
333
344
 
334
- # Makes it dead easy to do HTTP Token authentication.
345
+ # = HTTP \Token authentication
335
346
  #
336
- # Simple Token example:
347
+ # === Simple \Token example
337
348
  #
338
349
  # class PostsController < ApplicationController
339
350
  # TOKEN = "secret"
@@ -359,8 +370,8 @@ module ActionController
359
370
  # end
360
371
  #
361
372
  #
362
- # Here is a more advanced Token example where only Atom feeds and the XML API is protected by HTTP token authentication,
363
- # the regular HTML interface is protected by a session approach:
373
+ # Here is a more advanced Token example where only Atom feeds and the XML API are protected by HTTP token authentication.
374
+ # The regular HTML interface is protected by a session approach:
364
375
  #
365
376
  # class ApplicationController < ActionController::Base
366
377
  # before_action :set_account, :authenticate
@@ -412,14 +423,22 @@ module ActionController
412
423
  extend self
413
424
 
414
425
  module ControllerMethods
426
+ # Authenticate using an HTTP Bearer token, or otherwise render an HTTP
427
+ # header requesting the client to send a Bearer token.
428
+ #
429
+ # See ActionController::HttpAuthentication::Token for example usage.
415
430
  def authenticate_or_request_with_http_token(realm = "Application", message = nil, &login_procedure)
416
431
  authenticate_with_http_token(&login_procedure) || request_http_token_authentication(realm, message)
417
432
  end
418
433
 
434
+ # Authenticate using an HTTP Bearer token. Returns true if
435
+ # authentication is successful, false otherwise.
419
436
  def authenticate_with_http_token(&login_procedure)
420
437
  Token.authenticate(self, &login_procedure)
421
438
  end
422
439
 
440
+ # Render an HTTP header requesting the client to send a Bearer token for
441
+ # authentication.
423
442
  def request_http_token_authentication(realm = "Application", message = nil)
424
443
  Token.authentication_request(self, realm, message)
425
444
  end
@@ -428,17 +447,17 @@ module ActionController
428
447
  # If token Authorization header is present, call the login
429
448
  # procedure with the present token and options.
430
449
  #
431
- # [controller]
432
- # ActionController::Base instance for the current request.
450
+ # Returns the return value of <tt>login_procedure</tt> if a
451
+ # token is found. Returns <tt>nil</tt> if no token is found.
452
+ #
453
+ # ==== Parameters
433
454
  #
434
- # [login_procedure]
435
- # Proc to call if a token is present. The Proc should take two arguments:
455
+ # * +controller+ - ActionController::Base instance for the current request.
456
+ # * +login_procedure+ - Proc to call if a token is present. The Proc
457
+ # should take two arguments:
436
458
  #
437
459
  # authenticate(controller) { |token, options| ... }
438
460
  #
439
- # Returns the return value of <tt>login_procedure</tt> if a
440
- # token is found. Returns <tt>nil</tt> if no token is found.
441
-
442
461
  def authenticate(controller, &login_procedure)
443
462
  token, options = token_and_options(controller.request)
444
463
  unless token.blank?
@@ -449,14 +468,18 @@ module ActionController
449
468
  # Parses the token and options out of the token Authorization header.
450
469
  # The value for the Authorization header is expected to have the prefix
451
470
  # <tt>"Token"</tt> or <tt>"Bearer"</tt>. If the header looks like this:
471
+ #
452
472
  # Authorization: Token token="abc", nonce="def"
453
- # Then the returned token is <tt>"abc"</tt>, and the options are
454
- # <tt>{nonce: "def"}</tt>
455
473
  #
456
- # request - ActionDispatch::Request instance with the current headers.
474
+ # Then the returned token is <tt>"abc"</tt>, and the options are
475
+ # <tt>{nonce: "def"}</tt>.
457
476
  #
458
477
  # Returns an +Array+ of <tt>[String, Hash]</tt> if a token is present.
459
478
  # Returns +nil+ if no token is found.
479
+ #
480
+ # ==== Parameters
481
+ #
482
+ # * +request+ - ActionDispatch::Request instance with the current headers.
460
483
  def token_and_options(request)
461
484
  authorization_request = request.authorization.to_s
462
485
  if authorization_request[TOKEN_REGEX]
@@ -469,7 +492,7 @@ module ActionController
469
492
  rewrite_param_values params_array_from raw_params auth
470
493
  end
471
494
 
472
- # Takes raw_params and turns it into an array of parameters
495
+ # Takes +raw_params+ and turns it into an array of parameters.
473
496
  def params_array_from(raw_params)
474
497
  raw_params.map { |param| param.split %r/=(.+)?/ }
475
498
  end
@@ -494,10 +517,12 @@ module ActionController
494
517
 
495
518
  # Encodes the given token and options into an Authorization header value.
496
519
  #
497
- # token - String token.
498
- # options - optional Hash of the options.
499
- #
500
520
  # Returns String.
521
+ #
522
+ # ==== Parameters
523
+ #
524
+ # * +token+ - String token.
525
+ # * +options+ - Optional Hash of the options.
501
526
  def encode_credentials(token, options = {})
502
527
  values = ["#{TOKEN_KEY}#{token.to_s.inspect}"] + options.map do |key, value|
503
528
  "#{key}=#{value.to_s.inspect}"
@@ -507,10 +532,12 @@ module ActionController
507
532
 
508
533
  # Sets a WWW-Authenticate header to let the client know a token is desired.
509
534
  #
510
- # controller - ActionController::Base instance for the outgoing response.
511
- # realm - String realm to use in the header.
512
- #
513
535
  # Returns nothing.
536
+ #
537
+ # ==== Parameters
538
+ #
539
+ # * +controller+ - ActionController::Base instance for the outgoing response.
540
+ # * +realm+ - String realm to use in the header.
514
541
  def authentication_request(controller, realm, message = nil)
515
542
  message ||= "HTTP Token: Access denied.\n"
516
543
  controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.tr('"', "")}")
@@ -16,30 +16,6 @@ module ActionController
16
16
 
17
17
  attr_internal :view_runtime
18
18
 
19
- def process_action(*)
20
- raw_payload = {
21
- controller: self.class.name,
22
- action: action_name,
23
- request: request,
24
- params: request.filtered_parameters,
25
- headers: request.headers,
26
- format: request.format.ref,
27
- method: request.request_method,
28
- path: request.fullpath
29
- }
30
-
31
- ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload)
32
-
33
- ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
34
- result = super
35
- payload[:response] = response
36
- payload[:status] = response.status
37
- result
38
- ensure
39
- append_info_to_payload(payload)
40
- end
41
- end
42
-
43
19
  def render(*)
44
20
  render_output = nil
45
21
  self.view_runtime = cleanup_view_runtime do
@@ -70,37 +46,66 @@ module ActionController
70
46
  end
71
47
  end
72
48
 
73
- private
74
- # A hook invoked every time a before callback is halted.
75
- def halted_callback_hook(filter, _)
76
- ActiveSupport::Notifications.instrument("halted_callback.action_controller", filter: filter)
77
- end
49
+ private
50
+ def process_action(*)
51
+ ActiveSupport::ExecutionContext[:controller] = self
78
52
 
79
- # A hook which allows you to clean up any time, wrongly taken into account in
80
- # views, like database querying time.
81
- #
82
- # def cleanup_view_runtime
83
- # super - time_taken_in_something_expensive
84
- # end
85
- def cleanup_view_runtime # :doc:
86
- yield
87
- end
53
+ raw_payload = {
54
+ controller: self.class.name,
55
+ action: action_name,
56
+ request: request,
57
+ params: request.filtered_parameters,
58
+ headers: request.headers,
59
+ format: request.format.ref,
60
+ method: request.request_method,
61
+ path: request.fullpath
62
+ }
88
63
 
89
- # Every time after an action is processed, this method is invoked
90
- # with the payload, so you can add more information.
91
- def append_info_to_payload(payload) # :doc:
92
- payload[:view_runtime] = view_runtime
93
- end
64
+ ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload)
94
65
 
95
- module ClassMethods
96
- # A hook which allows other frameworks to log what happened during
97
- # controller process action. This method should return an array
98
- # with the messages to be added.
99
- def log_process_action(payload) #:nodoc:
100
- messages, view_runtime = [], payload[:view_runtime]
101
- messages << ("Views: %.1fms" % view_runtime.to_f) if view_runtime
102
- messages
66
+ ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
67
+ result = super
68
+ payload[:response] = response
69
+ payload[:status] = response.status
70
+ result
71
+ rescue => error
72
+ payload[:status] = ActionDispatch::ExceptionWrapper.status_code_for_exception(error.class.name)
73
+ raise
74
+ ensure
75
+ append_info_to_payload(payload)
76
+ end
77
+ end
78
+
79
+ # A hook invoked every time a before callback is halted.
80
+ def halted_callback_hook(filter, _)
81
+ ActiveSupport::Notifications.instrument("halted_callback.action_controller", filter: filter)
82
+ end
83
+
84
+ # A hook which allows you to clean up any time, wrongly taken into account in
85
+ # views, like database querying time.
86
+ #
87
+ # def cleanup_view_runtime
88
+ # super - time_taken_in_something_expensive
89
+ # end
90
+ def cleanup_view_runtime # :doc:
91
+ yield
92
+ end
93
+
94
+ # Every time after an action is processed, this method is invoked
95
+ # with the payload, so you can add more information.
96
+ def append_info_to_payload(payload) # :doc:
97
+ payload[:view_runtime] = view_runtime
98
+ end
99
+
100
+ module ClassMethods
101
+ # A hook which allows other frameworks to log what happened during
102
+ # controller process action. This method should return an array
103
+ # with the messages to be added.
104
+ def log_process_action(payload) # :nodoc:
105
+ messages, view_runtime = [], payload[:view_runtime]
106
+ messages << ("Views: %.1fms" % view_runtime.to_f) if view_runtime
107
+ messages
108
+ end
103
109
  end
104
- end
105
110
  end
106
111
  end
@@ -124,7 +124,7 @@ module ActionController
124
124
  class ClientDisconnected < RuntimeError
125
125
  end
126
126
 
127
- class Buffer < ActionDispatch::Response::Buffer #:nodoc:
127
+ class Buffer < ActionDispatch::Response::Buffer # :nodoc:
128
128
  include MonitorMixin
129
129
 
130
130
  class << self
@@ -168,6 +168,11 @@ module ActionController
168
168
  end
169
169
  end
170
170
 
171
+ # Same as +write+ but automatically include a newline at the end of the string.
172
+ def writeln(string)
173
+ write string.end_with?("\n") ? string : "#{string}\n"
174
+ end
175
+
171
176
  # Write a 'close' event to the buffer; the producer/writing thread
172
177
  # uses this to notify us that it's finished supplying content.
173
178
  #
@@ -225,7 +230,7 @@ module ActionController
225
230
  end
226
231
  end
227
232
 
228
- class Response < ActionDispatch::Response #:nodoc: all
233
+ class Response < ActionDispatch::Response # :nodoc: all
229
234
  private
230
235
  def before_committed
231
236
  super
@@ -256,6 +261,7 @@ module ActionController
256
261
  # Since we're processing the view in a different thread, copy the
257
262
  # thread locals from the main thread to the child thread. :'(
258
263
  locals.each { |k, v| t2[k] = v }
264
+ ActiveSupport::IsolatedExecutionState.share_with(t1)
259
265
 
260
266
  begin
261
267
  super(name)
@@ -291,6 +297,41 @@ module ActionController
291
297
  response.close if response
292
298
  end
293
299
 
300
+ # Sends a stream to the browser, which is helpful when you're generating exports or other running data where you
301
+ # don't want the entire file buffered in memory first. Similar to send_data, but where the data is generated live.
302
+ #
303
+ # Options:
304
+ # * <tt>:filename</tt> - suggests a filename for the browser to use.
305
+ # * <tt>:type</tt> - specifies an HTTP content type.
306
+ # You can specify either a string or a symbol for a registered type with <tt>Mime::Type.register</tt>, for example :json.
307
+ # If omitted, type will be inferred from the file extension specified in <tt>:filename</tt>.
308
+ # If no content type is registered for the extension, the default type 'application/octet-stream' will be used.
309
+ # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
310
+ # Valid values are 'inline' and 'attachment' (default).
311
+ #
312
+ # Example of generating a csv export:
313
+ #
314
+ # send_stream(filename: "subscribers.csv") do |stream|
315
+ # stream.write "email_address,updated_at\n"
316
+ #
317
+ # @subscribers.find_each do |subscriber|
318
+ # stream.write "#{subscriber.email_address},#{subscriber.updated_at}\n"
319
+ # end
320
+ # end
321
+ def send_stream(filename:, disposition: "attachment", type: nil)
322
+ response.headers["Content-Type"] =
323
+ (type.is_a?(Symbol) ? Mime[type].to_s : type) ||
324
+ Mime::Type.lookup_by_extension(File.extname(filename).downcase.delete(".")) ||
325
+ "application/octet-stream"
326
+
327
+ response.headers["Content-Disposition"] =
328
+ ActionDispatch::Http::ContentDisposition.format(disposition: disposition, filename: filename)
329
+
330
+ yield response.stream
331
+ ensure
332
+ response.stream.close
333
+ end
334
+
294
335
  private
295
336
  # Spawn a new thread to serve up the controller in. This is to get
296
337
  # around the fact that Rack isn't based around IOs and we need to use
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "abstract_controller/collector"
4
4
 
5
- module ActionController #:nodoc:
5
+ module ActionController # :nodoc:
6
6
  module MimeResponds
7
7
  # Without web-service support, an action which collects the data for displaying a list of people
8
8
  # might look something like this:
@@ -103,7 +103,7 @@ module ActionController #:nodoc:
103
103
  # If you need to use a MIME type which isn't supported by default, you can register your own handlers in
104
104
  # +config/initializers/mime_types.rb+ as follows.
105
105
  #
106
- # Mime::Type.register "image/jpg", :jpg
106
+ # Mime::Type.register "image/jpeg", :jpg
107
107
  #
108
108
  # +respond_to+ also allows you to specify a common block for different formats by using +any+:
109
109
  #
@@ -289,7 +289,7 @@ module ActionController #:nodoc:
289
289
  @format = request.negotiate_mime(@responses.keys)
290
290
  end
291
291
 
292
- class VariantCollector #:nodoc:
292
+ class VariantCollector # :nodoc:
293
293
  def initialize(variant = nil)
294
294
  @variant = variant
295
295
  @variants = {}