actionpack 4.2.11.1 → 5.2.4.3

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 (166) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +287 -488
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -7
  5. data/lib/abstract_controller/asset_paths.rb +2 -0
  6. data/lib/abstract_controller/base.rb +45 -49
  7. data/lib/{action_controller → abstract_controller}/caching/fragments.rb +78 -15
  8. data/lib/abstract_controller/caching.rb +66 -0
  9. data/lib/abstract_controller/callbacks.rb +47 -31
  10. data/lib/abstract_controller/collector.rb +8 -11
  11. data/lib/abstract_controller/error.rb +6 -0
  12. data/lib/abstract_controller/helpers.rb +25 -25
  13. data/lib/abstract_controller/logger.rb +2 -0
  14. data/lib/abstract_controller/railties/routes_helpers.rb +4 -2
  15. data/lib/abstract_controller/rendering.rb +42 -41
  16. data/lib/abstract_controller/translation.rb +10 -7
  17. data/lib/abstract_controller/url_for.rb +2 -0
  18. data/lib/abstract_controller.rb +12 -5
  19. data/lib/action_controller/api/api_rendering.rb +16 -0
  20. data/lib/action_controller/api.rb +149 -0
  21. data/lib/action_controller/base.rb +27 -19
  22. data/lib/action_controller/caching.rb +14 -57
  23. data/lib/action_controller/form_builder.rb +50 -0
  24. data/lib/action_controller/log_subscriber.rb +10 -15
  25. data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
  26. data/lib/action_controller/metal/conditional_get.rb +118 -44
  27. data/lib/action_controller/metal/content_security_policy.rb +52 -0
  28. data/lib/action_controller/metal/cookies.rb +3 -3
  29. data/lib/action_controller/metal/data_streaming.rb +27 -46
  30. data/lib/action_controller/metal/etag_with_flash.rb +18 -0
  31. data/lib/action_controller/metal/etag_with_template_digest.rb +20 -13
  32. data/lib/action_controller/metal/exceptions.rb +8 -14
  33. data/lib/action_controller/metal/flash.rb +4 -3
  34. data/lib/action_controller/metal/force_ssl.rb +23 -21
  35. data/lib/action_controller/metal/head.rb +21 -19
  36. data/lib/action_controller/metal/helpers.rb +24 -14
  37. data/lib/action_controller/metal/http_authentication.rb +64 -57
  38. data/lib/action_controller/metal/implicit_render.rb +62 -8
  39. data/lib/action_controller/metal/instrumentation.rb +19 -21
  40. data/lib/action_controller/metal/live.rb +90 -106
  41. data/lib/action_controller/metal/mime_responds.rb +33 -46
  42. data/lib/action_controller/metal/parameter_encoding.rb +51 -0
  43. data/lib/action_controller/metal/params_wrapper.rb +61 -53
  44. data/lib/action_controller/metal/redirecting.rb +49 -28
  45. data/lib/action_controller/metal/renderers.rb +87 -44
  46. data/lib/action_controller/metal/rendering.rb +72 -50
  47. data/lib/action_controller/metal/request_forgery_protection.rb +229 -93
  48. data/lib/action_controller/metal/rescue.rb +9 -16
  49. data/lib/action_controller/metal/streaming.rb +12 -10
  50. data/lib/action_controller/metal/strong_parameters.rb +583 -164
  51. data/lib/action_controller/metal/testing.rb +2 -17
  52. data/lib/action_controller/metal/url_for.rb +19 -10
  53. data/lib/action_controller/metal.rb +98 -83
  54. data/lib/action_controller/railtie.rb +28 -10
  55. data/lib/action_controller/railties/helpers.rb +2 -0
  56. data/lib/action_controller/renderer.rb +117 -0
  57. data/lib/action_controller/template_assertions.rb +11 -0
  58. data/lib/action_controller/test_case.rb +280 -411
  59. data/lib/action_controller.rb +29 -21
  60. data/lib/action_dispatch/http/cache.rb +93 -47
  61. data/lib/action_dispatch/http/content_security_policy.rb +272 -0
  62. data/lib/action_dispatch/http/filter_parameters.rb +26 -20
  63. data/lib/action_dispatch/http/filter_redirect.rb +10 -11
  64. data/lib/action_dispatch/http/headers.rb +55 -22
  65. data/lib/action_dispatch/http/mime_negotiation.rb +56 -41
  66. data/lib/action_dispatch/http/mime_type.rb +134 -121
  67. data/lib/action_dispatch/http/mime_types.rb +20 -6
  68. data/lib/action_dispatch/http/parameter_filter.rb +25 -11
  69. data/lib/action_dispatch/http/parameters.rb +98 -39
  70. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  71. data/lib/action_dispatch/http/request.rb +200 -118
  72. data/lib/action_dispatch/http/response.rb +225 -110
  73. data/lib/action_dispatch/http/upload.rb +12 -6
  74. data/lib/action_dispatch/http/url.rb +110 -28
  75. data/lib/action_dispatch/journey/formatter.rb +55 -32
  76. data/lib/action_dispatch/journey/gtg/builder.rb +7 -5
  77. data/lib/action_dispatch/journey/gtg/simulator.rb +3 -9
  78. data/lib/action_dispatch/journey/gtg/transition_table.rb +17 -16
  79. data/lib/action_dispatch/journey/nfa/builder.rb +5 -3
  80. data/lib/action_dispatch/journey/nfa/dot.rb +13 -13
  81. data/lib/action_dispatch/journey/nfa/simulator.rb +3 -1
  82. data/lib/action_dispatch/journey/nfa/transition_table.rb +5 -48
  83. data/lib/action_dispatch/journey/nodes/node.rb +18 -6
  84. data/lib/action_dispatch/journey/parser.rb +23 -22
  85. data/lib/action_dispatch/journey/parser.y +3 -2
  86. data/lib/action_dispatch/journey/parser_extras.rb +12 -4
  87. data/lib/action_dispatch/journey/path/pattern.rb +50 -44
  88. data/lib/action_dispatch/journey/route.rb +106 -28
  89. data/lib/action_dispatch/journey/router/utils.rb +20 -11
  90. data/lib/action_dispatch/journey/router.rb +35 -23
  91. data/lib/action_dispatch/journey/routes.rb +18 -16
  92. data/lib/action_dispatch/journey/scanner.rb +18 -15
  93. data/lib/action_dispatch/journey/visitors.rb +99 -52
  94. data/lib/action_dispatch/journey.rb +7 -5
  95. data/lib/action_dispatch/middleware/callbacks.rb +1 -2
  96. data/lib/action_dispatch/middleware/cookies.rb +304 -193
  97. data/lib/action_dispatch/middleware/debug_exceptions.rb +152 -57
  98. data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
  99. data/lib/action_dispatch/middleware/exception_wrapper.rb +68 -69
  100. data/lib/action_dispatch/middleware/executor.rb +21 -0
  101. data/lib/action_dispatch/middleware/flash.rb +78 -54
  102. data/lib/action_dispatch/middleware/public_exceptions.rb +27 -25
  103. data/lib/action_dispatch/middleware/reloader.rb +5 -91
  104. data/lib/action_dispatch/middleware/remote_ip.rb +41 -31
  105. data/lib/action_dispatch/middleware/request_id.rb +17 -9
  106. data/lib/action_dispatch/middleware/session/abstract_store.rb +41 -25
  107. data/lib/action_dispatch/middleware/session/cache_store.rb +24 -14
  108. data/lib/action_dispatch/middleware/session/cookie_store.rb +72 -67
  109. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -2
  110. data/lib/action_dispatch/middleware/show_exceptions.rb +26 -22
  111. data/lib/action_dispatch/middleware/ssl.rb +114 -36
  112. data/lib/action_dispatch/middleware/stack.rb +31 -44
  113. data/lib/action_dispatch/middleware/static.rb +57 -50
  114. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +2 -14
  115. data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +0 -0
  116. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  117. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +21 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +13 -0
  119. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +1 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -1
  121. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  122. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
  123. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +64 -64
  124. data/lib/action_dispatch/railtie.rb +19 -11
  125. data/lib/action_dispatch/request/session.rb +106 -59
  126. data/lib/action_dispatch/request/utils.rb +67 -24
  127. data/lib/action_dispatch/routing/endpoint.rb +9 -2
  128. data/lib/action_dispatch/routing/inspector.rb +58 -67
  129. data/lib/action_dispatch/routing/mapper.rb +733 -447
  130. data/lib/action_dispatch/routing/polymorphic_routes.rb +161 -139
  131. data/lib/action_dispatch/routing/redirection.rb +36 -26
  132. data/lib/action_dispatch/routing/route_set.rb +321 -291
  133. data/lib/action_dispatch/routing/routes_proxy.rb +32 -5
  134. data/lib/action_dispatch/routing/url_for.rb +65 -25
  135. data/lib/action_dispatch/routing.rb +17 -18
  136. data/lib/action_dispatch/system_test_case.rb +147 -0
  137. data/lib/action_dispatch/system_testing/browser.rb +49 -0
  138. data/lib/action_dispatch/system_testing/driver.rb +59 -0
  139. data/lib/action_dispatch/system_testing/server.rb +31 -0
  140. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +96 -0
  141. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +31 -0
  142. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +26 -0
  143. data/lib/action_dispatch/testing/assertion_response.rb +47 -0
  144. data/lib/action_dispatch/testing/assertions/response.rb +45 -20
  145. data/lib/action_dispatch/testing/assertions/routing.rb +30 -26
  146. data/lib/action_dispatch/testing/assertions.rb +6 -4
  147. data/lib/action_dispatch/testing/integration.rb +347 -209
  148. data/lib/action_dispatch/testing/request_encoder.rb +55 -0
  149. data/lib/action_dispatch/testing/test_process.rb +28 -22
  150. data/lib/action_dispatch/testing/test_request.rb +27 -34
  151. data/lib/action_dispatch/testing/test_response.rb +35 -7
  152. data/lib/action_dispatch.rb +27 -19
  153. data/lib/action_pack/gem_version.rb +5 -3
  154. data/lib/action_pack/version.rb +3 -1
  155. data/lib/action_pack.rb +4 -2
  156. metadata +56 -38
  157. data/lib/action_controller/metal/hide_actions.rb +0 -40
  158. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  159. data/lib/action_controller/middleware.rb +0 -39
  160. data/lib/action_controller/model_naming.rb +0 -12
  161. data/lib/action_dispatch/journey/backwards.rb +0 -5
  162. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  163. data/lib/action_dispatch/middleware/params_parser.rb +0 -60
  164. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  165. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  166. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -1,5 +1,7 @@
1
- require 'base64'
2
- require 'active_support/security_utils'
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+ require "active_support/security_utils"
3
5
 
4
6
  module ActionController
5
7
  # Makes it dead easy to do HTTP Basic, Digest and Token authentication.
@@ -28,14 +30,14 @@ module ActionController
28
30
  # class ApplicationController < ActionController::Base
29
31
  # before_action :set_account, :authenticate
30
32
  #
31
- # protected
33
+ # private
32
34
  # def set_account
33
35
  # @account = Account.find_by(url_name: request.subdomains.first)
34
36
  # end
35
37
  #
36
38
  # def authenticate
37
39
  # case request.format
38
- # when Mime::XML, Mime::ATOM
40
+ # when Mime[:xml], Mime[:atom]
39
41
  # if user = authenticate_with_http_basic { |u, p| @account.users.authenticate(u, p) }
40
42
  # @current_user = user
41
43
  # else
@@ -70,25 +72,25 @@ module ActionController
70
72
  before_action(options.except(:name, :password, :realm)) do
71
73
  authenticate_or_request_with_http_basic(options[:realm] || "Application") do |name, password|
72
74
  # This comparison uses & so that it doesn't short circuit and
73
- # uses `variable_size_secure_compare` so that length information
75
+ # uses `secure_compare` so that length information
74
76
  # isn't leaked.
75
- ActiveSupport::SecurityUtils.variable_size_secure_compare(name, options[:name]) &
76
- ActiveSupport::SecurityUtils.variable_size_secure_compare(password, options[:password])
77
+ ActiveSupport::SecurityUtils.secure_compare(name, options[:name]) &
78
+ ActiveSupport::SecurityUtils.secure_compare(password, options[:password])
77
79
  end
78
80
  end
79
81
  end
80
82
  end
81
83
 
82
- def authenticate_or_request_with_http_basic(realm = "Application", &login_procedure)
83
- authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm)
84
+ def authenticate_or_request_with_http_basic(realm = "Application", message = nil, &login_procedure)
85
+ authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm, message)
84
86
  end
85
87
 
86
88
  def authenticate_with_http_basic(&login_procedure)
87
89
  HttpAuthentication::Basic.authenticate(request, &login_procedure)
88
90
  end
89
91
 
90
- def request_http_basic_authentication(realm = "Application")
91
- HttpAuthentication::Basic.authentication_request(self, realm)
92
+ def request_http_basic_authentication(realm = "Application", message = nil)
93
+ HttpAuthentication::Basic.authentication_request(self, realm, message)
92
94
  end
93
95
  end
94
96
 
@@ -99,33 +101,34 @@ module ActionController
99
101
  end
100
102
 
101
103
  def has_basic_credentials?(request)
102
- request.authorization.present? && (auth_scheme(request) == 'Basic')
104
+ request.authorization.present? && (auth_scheme(request).downcase == "basic")
103
105
  end
104
106
 
105
107
  def user_name_and_password(request)
106
- decode_credentials(request).split(':', 2)
108
+ decode_credentials(request).split(":", 2)
107
109
  end
108
110
 
109
111
  def decode_credentials(request)
110
- ::Base64.decode64(auth_param(request) || '')
112
+ ::Base64.decode64(auth_param(request) || "")
111
113
  end
112
114
 
113
115
  def auth_scheme(request)
114
- request.authorization.split(' ', 2).first
116
+ request.authorization.to_s.split(" ", 2).first
115
117
  end
116
118
 
117
119
  def auth_param(request)
118
- request.authorization.split(' ', 2).second
120
+ request.authorization.to_s.split(" ", 2).second
119
121
  end
120
122
 
121
123
  def encode_credentials(user_name, password)
122
124
  "Basic #{::Base64.strict_encode64("#{user_name}:#{password}")}"
123
125
  end
124
126
 
125
- def authentication_request(controller, realm)
126
- controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.gsub(/"/, "")}")
127
+ def authentication_request(controller, realm, message)
128
+ message ||= "HTTP Basic: Access denied.\n"
129
+ controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.tr('"'.freeze, "".freeze)}")
127
130
  controller.status = 401
128
- controller.response_body = "HTTP Basic: Access denied.\n"
131
+ controller.response_body = message
129
132
  end
130
133
  end
131
134
 
@@ -175,8 +178,8 @@ module ActionController
175
178
  extend self
176
179
 
177
180
  module ControllerMethods
178
- def authenticate_or_request_with_http_digest(realm = "Application", &password_procedure)
179
- authenticate_with_http_digest(realm, &password_procedure) || request_http_digest_authentication(realm)
181
+ def authenticate_or_request_with_http_digest(realm = "Application", message = nil, &password_procedure)
182
+ authenticate_with_http_digest(realm, &password_procedure) || request_http_digest_authentication(realm, message)
180
183
  end
181
184
 
182
185
  # Authenticate with HTTP Digest, returns true or false
@@ -207,7 +210,7 @@ module ActionController
207
210
  password = password_procedure.call(credentials[:username])
208
211
  return false unless password
209
212
 
210
- method = request.env['rack.methodoverride.original_method'] || request.env['REQUEST_METHOD']
213
+ method = request.get_header("rack.methodoverride.original_method") || request.get_header("REQUEST_METHOD")
211
214
  uri = credentials[:uri]
212
215
 
213
216
  [true, false].any? do |trailing_question_mark|
@@ -223,19 +226,19 @@ module ActionController
223
226
  # Returns the expected response for a request of +http_method+ to +uri+ with the decoded +credentials+ and the expected +password+
224
227
  # Optional parameter +password_is_ha1+ is set to +true+ by default, since best practice is to store ha1 digest instead
225
228
  # of a plain-text password.
226
- def expected_response(http_method, uri, credentials, password, password_is_ha1=true)
229
+ def expected_response(http_method, uri, credentials, password, password_is_ha1 = true)
227
230
  ha1 = password_is_ha1 ? password : ha1(credentials, password)
228
- ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(':'))
229
- ::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(':'))
231
+ ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(":"))
232
+ ::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(":"))
230
233
  end
231
234
 
232
235
  def ha1(credentials, password)
233
- ::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(':'))
236
+ ::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(":"))
234
237
  end
235
238
 
236
239
  def encode_credentials(http_method, credentials, password, password_is_ha1)
237
240
  credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password, password_is_ha1)
238
- "Digest " + credentials.sort_by {|x| x[0].to_s }.map {|v| "#{v[0]}='#{v[1]}'" }.join(', ')
241
+ "Digest " + credentials.sort_by { |x| x[0].to_s }.map { |v| "#{v[0]}='#{v[1]}'" }.join(", ")
239
242
  end
240
243
 
241
244
  def decode_credentials_header(request)
@@ -243,9 +246,9 @@ module ActionController
243
246
  end
244
247
 
245
248
  def decode_credentials(header)
246
- ActiveSupport::HashWithIndifferentAccess[header.to_s.gsub(/^Digest\s+/, '').split(',').map do |pair|
247
- key, value = pair.split('=', 2)
248
- [key.strip, value.to_s.gsub(/^"|"$/,'').delete('\'')]
249
+ ActiveSupport::HashWithIndifferentAccess[header.to_s.gsub(/^Digest\s+/, "").split(",").map do |pair|
250
+ key, value = pair.split("=", 2)
251
+ [key.strip, value.to_s.gsub(/^"|"$/, "").delete("'")]
249
252
  end]
250
253
  end
251
254
 
@@ -264,8 +267,8 @@ module ActionController
264
267
  end
265
268
 
266
269
  def secret_token(request)
267
- key_generator = request.env["action_dispatch.key_generator"]
268
- http_auth_salt = request.env["action_dispatch.http_auth_salt"]
270
+ key_generator = request.key_generator
271
+ http_auth_salt = request.http_auth_salt
269
272
  key_generator.generate_key(http_auth_salt)
270
273
  end
271
274
 
@@ -309,21 +312,20 @@ module ActionController
309
312
  end
310
313
 
311
314
  # Might want a shorter timeout depending on whether the request
312
- # is a PATCH, PUT, or POST, and if client is browser or web service.
315
+ # is a PATCH, PUT, or POST, and if the client is a browser or web service.
313
316
  # Can be much shorter if the Stale directive is implemented. This would
314
- # allow a user to use new nonce without prompting user again for their
317
+ # allow a user to use new nonce without prompting the user again for their
315
318
  # username and password.
316
- def validate_nonce(secret_key, request, value, seconds_to_timeout=5*60)
319
+ def validate_nonce(secret_key, request, value, seconds_to_timeout = 5 * 60)
317
320
  return false if value.nil?
318
321
  t = ::Base64.decode64(value).split(":").first.to_i
319
322
  nonce(secret_key, t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout
320
323
  end
321
324
 
322
- # Opaque based on random generation - but changing each request?
325
+ # Opaque based on digest of secret key
323
326
  def opaque(secret_key)
324
327
  ::Digest::MD5.hexdigest(secret_key)
325
328
  end
326
-
327
329
  end
328
330
 
329
331
  # Makes it dead easy to do HTTP Token authentication.
@@ -346,7 +348,9 @@ module ActionController
346
348
  # private
347
349
  # def authenticate
348
350
  # authenticate_or_request_with_http_token do |token, options|
349
- # token == TOKEN
351
+ # # Compare the tokens in a time-constant manner, to mitigate
352
+ # # timing attacks.
353
+ # ActiveSupport::SecurityUtils.secure_compare(token, TOKEN)
350
354
  # end
351
355
  # end
352
356
  # end
@@ -358,14 +362,14 @@ module ActionController
358
362
  # class ApplicationController < ActionController::Base
359
363
  # before_action :set_account, :authenticate
360
364
  #
361
- # protected
365
+ # private
362
366
  # def set_account
363
367
  # @account = Account.find_by(url_name: request.subdomains.first)
364
368
  # end
365
369
  #
366
370
  # def authenticate
367
371
  # case request.format
368
- # when Mime::XML, Mime::ATOM
372
+ # when Mime[:xml], Mime[:atom]
369
373
  # if user = authenticate_with_http_token { |t, o| @account.users.authenticate(t, o) }
370
374
  # @current_user = user
371
375
  # else
@@ -400,22 +404,22 @@ module ActionController
400
404
  #
401
405
  # RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
402
406
  module Token
403
- TOKEN_KEY = 'token='
404
- TOKEN_REGEX = /^Token /
407
+ TOKEN_KEY = "token="
408
+ TOKEN_REGEX = /^(Token|Bearer)\s+/
405
409
  AUTHN_PAIR_DELIMITERS = /(?:,|;|\t+)/
406
410
  extend self
407
411
 
408
412
  module ControllerMethods
409
- def authenticate_or_request_with_http_token(realm = "Application", &login_procedure)
410
- authenticate_with_http_token(&login_procedure) || request_http_token_authentication(realm)
413
+ def authenticate_or_request_with_http_token(realm = "Application", message = nil, &login_procedure)
414
+ authenticate_with_http_token(&login_procedure) || request_http_token_authentication(realm, message)
411
415
  end
412
416
 
413
417
  def authenticate_with_http_token(&login_procedure)
414
418
  Token.authenticate(self, &login_procedure)
415
419
  end
416
420
 
417
- def request_http_token_authentication(realm = "Application")
418
- Token.authentication_request(self, realm)
421
+ def request_http_token_authentication(realm = "Application", message = nil)
422
+ Token.authentication_request(self, realm, message)
419
423
  end
420
424
  end
421
425
 
@@ -440,15 +444,17 @@ module ActionController
440
444
  end
441
445
  end
442
446
 
443
- # Parses the token and options out of the token authorization header. If
444
- # the header looks like this:
447
+ # Parses the token and options out of the token Authorization header.
448
+ # The value for the Authorization header is expected to have the prefix
449
+ # <tt>"Token"</tt> or <tt>"Bearer"</tt>. If the header looks like this:
445
450
  # Authorization: Token token="abc", nonce="def"
446
- # Then the returned token is "abc", and the options is {nonce: "def"}
451
+ # Then the returned token is <tt>"abc"</tt>, and the options are
452
+ # <tt>{nonce: "def"}</tt>
447
453
  #
448
454
  # request - ActionDispatch::Request instance with the current headers.
449
455
  #
450
- # Returns an Array of [String, Hash] if a token is present.
451
- # Returns nil if no token is found.
456
+ # Returns an +Array+ of <tt>[String, Hash]</tt> if a token is present.
457
+ # Returns +nil+ if no token is found.
452
458
  def token_and_options(request)
453
459
  authorization_request = request.authorization.to_s
454
460
  if authorization_request[TOKEN_REGEX]
@@ -468,14 +474,14 @@ module ActionController
468
474
 
469
475
  # This removes the <tt>"</tt> characters wrapping the value.
470
476
  def rewrite_param_values(array_params)
471
- array_params.each { |param| (param[1] || "").gsub! %r/^"|"$/, '' }
477
+ array_params.each { |param| (param[1] || "".dup).gsub! %r/^"|"$/, "" }
472
478
  end
473
479
 
474
480
  # This method takes an authorization body and splits up the key-value
475
481
  # pairs by the standardized <tt>:</tt>, <tt>;</tt>, or <tt>\t</tt>
476
482
  # delimiters defined in +AUTHN_PAIR_DELIMITERS+.
477
483
  def raw_params(auth)
478
- _raw_params = auth.sub(TOKEN_REGEX, '').split(/\s*#{AUTHN_PAIR_DELIMITERS}\s*/)
484
+ _raw_params = auth.sub(TOKEN_REGEX, "").split(/\s*#{AUTHN_PAIR_DELIMITERS}\s*/)
479
485
 
480
486
  if !(_raw_params.first =~ %r{\A#{TOKEN_KEY}})
481
487
  _raw_params[0] = "#{TOKEN_KEY}#{_raw_params.first}"
@@ -497,15 +503,16 @@ module ActionController
497
503
  "Token #{values * ", "}"
498
504
  end
499
505
 
500
- # Sets a WWW-Authenticate to let the client know a token is desired.
506
+ # Sets a WWW-Authenticate header to let the client know a token is desired.
501
507
  #
502
508
  # controller - ActionController::Base instance for the outgoing response.
503
509
  # realm - String realm to use in the header.
504
510
  #
505
511
  # Returns nothing.
506
- def authentication_request(controller, realm)
507
- controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.gsub(/"/, "")}")
508
- controller.__send__ :render, :text => "HTTP Token: Access denied.\n", :status => :unauthorized
512
+ def authentication_request(controller, realm, message = nil)
513
+ message ||= "HTTP Token: Access denied.\n"
514
+ controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.tr('"'.freeze, "".freeze)}")
515
+ controller.__send__ :render, plain: message, status: :unauthorized
509
516
  end
510
517
  end
511
518
  end
@@ -1,19 +1,73 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionController
4
+ # Handles implicit rendering for a controller action that does not
5
+ # explicitly respond with +render+, +respond_to+, +redirect+, or +head+.
6
+ #
7
+ # For API controllers, the implicit response is always <tt>204 No Content</tt>.
8
+ #
9
+ # For all other controllers, we use these heuristics to decide whether to
10
+ # render a template, raise an error for a missing template, or respond with
11
+ # <tt>204 No Content</tt>:
12
+ #
13
+ # First, if we DO find a template, it's rendered. Template lookup accounts
14
+ # for the action name, locales, format, variant, template handlers, and more
15
+ # (see +render+ for details).
16
+ #
17
+ # Second, if we DON'T find a template but the controller action does have
18
+ # templates for other formats, variants, etc., then we trust that you meant
19
+ # to provide a template for this response, too, and we raise
20
+ # <tt>ActionController::UnknownFormat</tt> with an explanation.
21
+ #
22
+ # Third, if we DON'T find a template AND the request is a page load in a web
23
+ # browser (technically, a non-XHR GET request for an HTML response) where
24
+ # you reasonably expect to have rendered a template, then we raise
25
+ # <tt>ActionView::UnknownFormat</tt> with an explanation.
26
+ #
27
+ # Finally, if we DON'T find a template AND the request isn't a browser page
28
+ # load, then we implicitly respond with <tt>204 No Content</tt>.
2
29
  module ImplicitRender
3
- def send_action(method, *args)
4
- ret = super
5
- default_render unless performed?
6
- ret
7
- end
30
+ # :stopdoc:
31
+ include BasicImplicitRender
8
32
 
9
33
  def default_render(*args)
10
- render(*args)
34
+ if template_exists?(action_name.to_s, _prefixes, variants: request.variant)
35
+ render(*args)
36
+ elsif any_templates?(action_name.to_s, _prefixes)
37
+ message = "#{self.class.name}\##{action_name} is missing a template " \
38
+ "for this request format and variant.\n" \
39
+ "\nrequest.formats: #{request.formats.map(&:to_s).inspect}" \
40
+ "\nrequest.variant: #{request.variant.inspect}"
41
+
42
+ raise ActionController::UnknownFormat, message
43
+ elsif interactive_browser_request?
44
+ message = "#{self.class.name}\##{action_name} is missing a template " \
45
+ "for this request format and variant.\n\n" \
46
+ "request.formats: #{request.formats.map(&:to_s).inspect}\n" \
47
+ "request.variant: #{request.variant.inspect}\n\n" \
48
+ "NOTE! For XHR/Ajax or API requests, this action would normally " \
49
+ "respond with 204 No Content: an empty white screen. Since you're " \
50
+ "loading it in a web browser, we assume that you expected to " \
51
+ "actually render a template, not nothing, so we're showing an " \
52
+ "error to be extra-clear. If you expect 204 No Content, carry on. " \
53
+ "That's what you'll get from an XHR or API request. Give it a shot."
54
+
55
+ raise ActionController::UnknownFormat, message
56
+ else
57
+ logger.info "No template found for #{self.class.name}\##{action_name}, rendering head :no_content" if logger
58
+ super
59
+ end
11
60
  end
12
61
 
13
62
  def method_for_action(action_name)
14
63
  super || if template_exists?(action_name.to_s, _prefixes)
15
- "default_render"
16
- end
64
+ "default_render"
65
+ end
17
66
  end
67
+
68
+ private
69
+ def interactive_browser_request?
70
+ request.get? && request.format == Mime[:html] && !request.xhr?
71
+ end
18
72
  end
19
73
  end
@@ -1,9 +1,11 @@
1
- require 'benchmark'
2
- require 'abstract_controller/logger'
1
+ # frozen_string_literal: true
2
+
3
+ require "benchmark"
4
+ require "abstract_controller/logger"
3
5
 
4
6
  module ActionController
5
7
  # Adds instrumentation to several ends in ActionController::Base. It also provides
6
- # some hooks related with process_action, this allows an ORM like Active Record
8
+ # some hooks related with process_action. This allows an ORM like Active Record
7
9
  # and/or DataMapper to plug in ActionController and show related information.
8
10
  #
9
11
  # Check ActiveRecord::Railties::ControllerRuntime for an example.
@@ -11,18 +13,18 @@ module ActionController
11
13
  extend ActiveSupport::Concern
12
14
 
13
15
  include AbstractController::Logger
14
- include ActionController::RackDelegation
15
16
 
16
17
  attr_internal :view_runtime
17
18
 
18
19
  def process_action(*args)
19
20
  raw_payload = {
20
- :controller => self.class.name,
21
- :action => self.action_name,
22
- :params => request.filtered_parameters,
23
- :format => request.format.try(:ref),
24
- :method => request.request_method,
25
- :path => (request.fullpath rescue "unknown")
21
+ controller: self.class.name,
22
+ action: action_name,
23
+ params: request.filtered_parameters,
24
+ headers: request.headers,
25
+ format: request.format.ref,
26
+ method: request.request_method,
27
+ path: request.fullpath
26
28
  }
27
29
 
28
30
  ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup)
@@ -46,9 +48,9 @@ module ActionController
46
48
  render_output
47
49
  end
48
50
 
49
- def send_file(path, options={})
51
+ def send_file(path, options = {})
50
52
  ActiveSupport::Notifications.instrument("send_file.action_controller",
51
- options.merge(:path => path)) do
53
+ options.merge(path: path)) do
52
54
  super
53
55
  end
54
56
  end
@@ -72,25 +74,22 @@ module ActionController
72
74
 
73
75
  # A hook invoked every time a before callback is halted.
74
76
  def halted_callback_hook(filter)
75
- ActiveSupport::Notifications.instrument("halted_callback.action_controller", :filter => filter)
77
+ ActiveSupport::Notifications.instrument("halted_callback.action_controller", filter: filter)
76
78
  end
77
79
 
78
- # A hook which allows you to clean up any time taken into account in
79
- # views wrongly, like database querying time.
80
+ # A hook which allows you to clean up any time, wrongly taken into account in
81
+ # views, like database querying time.
80
82
  #
81
83
  # def cleanup_view_runtime
82
84
  # super - time_taken_in_something_expensive
83
85
  # end
84
- #
85
- # :api: plugin
86
- def cleanup_view_runtime #:nodoc:
86
+ def cleanup_view_runtime # :doc:
87
87
  yield
88
88
  end
89
89
 
90
90
  # Every time after an action is processed, this method is invoked
91
91
  # with the payload, so you can add more information.
92
- # :api: plugin
93
- def append_info_to_payload(payload) #:nodoc:
92
+ def append_info_to_payload(payload) # :doc:
94
93
  payload[:view_runtime] = view_runtime
95
94
  end
96
95
 
@@ -98,7 +97,6 @@ module ActionController
98
97
  # A hook which allows other frameworks to log what happened during
99
98
  # controller process action. This method should return an array
100
99
  # with the messages to be added.
101
- # :api: plugin
102
100
  def log_process_action(payload) #:nodoc:
103
101
  messages, view_runtime = [], payload[:view_runtime]
104
102
  messages << ("Views: %.1fms" % view_runtime.to_f) if view_runtime