actionpack 5.2.6 → 6.1.4.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 (155) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +327 -335
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -3
  5. data/lib/abstract_controller/base.rb +38 -4
  6. data/lib/abstract_controller/caching/fragments.rb +6 -22
  7. data/lib/abstract_controller/caching.rb +1 -1
  8. data/lib/abstract_controller/callbacks.rb +14 -2
  9. data/lib/abstract_controller/collector.rb +1 -2
  10. data/lib/abstract_controller/helpers.rb +106 -90
  11. data/lib/abstract_controller/railties/routes_helpers.rb +17 -1
  12. data/lib/abstract_controller/rendering.rb +9 -9
  13. data/lib/abstract_controller/translation.rb +11 -5
  14. data/lib/abstract_controller.rb +1 -0
  15. data/lib/action_controller/api.rb +4 -3
  16. data/lib/action_controller/base.rb +6 -9
  17. data/lib/action_controller/caching.rb +1 -3
  18. data/lib/action_controller/log_subscriber.rb +10 -7
  19. data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
  20. data/lib/action_controller/metal/conditional_get.rb +19 -5
  21. data/lib/action_controller/metal/content_security_policy.rb +1 -2
  22. data/lib/action_controller/metal/cookies.rb +3 -1
  23. data/lib/action_controller/metal/data_streaming.rb +6 -7
  24. data/lib/action_controller/metal/default_headers.rb +17 -0
  25. data/lib/action_controller/metal/etag_with_template_digest.rb +4 -6
  26. data/lib/action_controller/metal/exceptions.rb +56 -2
  27. data/lib/action_controller/metal/flash.rb +5 -5
  28. data/lib/action_controller/metal/head.rb +7 -4
  29. data/lib/action_controller/metal/helpers.rb +14 -5
  30. data/lib/action_controller/metal/http_authentication.rb +24 -23
  31. data/lib/action_controller/metal/implicit_render.rb +5 -15
  32. data/lib/action_controller/metal/instrumentation.rb +13 -14
  33. data/lib/action_controller/metal/live.rb +39 -32
  34. data/lib/action_controller/metal/logging.rb +20 -0
  35. data/lib/action_controller/metal/mime_responds.rb +19 -4
  36. data/lib/action_controller/metal/parameter_encoding.rb +35 -4
  37. data/lib/action_controller/metal/params_wrapper.rb +32 -22
  38. data/lib/action_controller/metal/permissions_policy.rb +46 -0
  39. data/lib/action_controller/metal/redirecting.rb +6 -6
  40. data/lib/action_controller/metal/renderers.rb +4 -4
  41. data/lib/action_controller/metal/rendering.rb +8 -3
  42. data/lib/action_controller/metal/request_forgery_protection.rb +26 -49
  43. data/lib/action_controller/metal/rescue.rb +1 -1
  44. data/lib/action_controller/metal/streaming.rb +0 -1
  45. data/lib/action_controller/metal/strong_parameters.rb +167 -58
  46. data/lib/action_controller/metal/url_for.rb +1 -1
  47. data/lib/action_controller/metal.rb +10 -8
  48. data/lib/action_controller/railties/helpers.rb +1 -1
  49. data/lib/action_controller/renderer.rb +37 -13
  50. data/lib/action_controller/template_assertions.rb +1 -1
  51. data/lib/action_controller/test_case.rb +71 -63
  52. data/lib/action_controller.rb +7 -4
  53. data/lib/action_dispatch/http/cache.rb +31 -27
  54. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  55. data/lib/action_dispatch/http/content_security_policy.rb +33 -19
  56. data/lib/action_dispatch/http/filter_parameters.rb +9 -8
  57. data/lib/action_dispatch/http/filter_redirect.rb +2 -3
  58. data/lib/action_dispatch/http/headers.rb +4 -4
  59. data/lib/action_dispatch/http/mime_negotiation.rb +26 -13
  60. data/lib/action_dispatch/http/mime_type.rb +43 -24
  61. data/lib/action_dispatch/http/parameters.rb +14 -23
  62. data/lib/action_dispatch/http/permissions_policy.rb +173 -0
  63. data/lib/action_dispatch/http/request.rb +45 -22
  64. data/lib/action_dispatch/http/response.rb +45 -25
  65. data/lib/action_dispatch/http/upload.rb +9 -1
  66. data/lib/action_dispatch/http/url.rb +82 -82
  67. data/lib/action_dispatch/journey/formatter.rb +55 -31
  68. data/lib/action_dispatch/journey/gtg/builder.rb +22 -37
  69. data/lib/action_dispatch/journey/gtg/simulator.rb +8 -7
  70. data/lib/action_dispatch/journey/gtg/transition_table.rb +6 -5
  71. data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
  72. data/lib/action_dispatch/journey/nodes/node.rb +13 -11
  73. data/lib/action_dispatch/journey/parser.rb +13 -13
  74. data/lib/action_dispatch/journey/parser.y +1 -1
  75. data/lib/action_dispatch/journey/path/pattern.rb +19 -21
  76. data/lib/action_dispatch/journey/route.rb +10 -20
  77. data/lib/action_dispatch/journey/router/utils.rb +14 -12
  78. data/lib/action_dispatch/journey/router.rb +26 -34
  79. data/lib/action_dispatch/journey/routes.rb +0 -2
  80. data/lib/action_dispatch/journey/scanner.rb +10 -4
  81. data/lib/action_dispatch/journey/visitors.rb +1 -4
  82. data/lib/action_dispatch/journey.rb +0 -2
  83. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  84. data/lib/action_dispatch/middleware/callbacks.rb +2 -4
  85. data/lib/action_dispatch/middleware/cookies.rb +128 -109
  86. data/lib/action_dispatch/middleware/debug_exceptions.rb +43 -66
  87. data/lib/action_dispatch/middleware/debug_locks.rb +5 -5
  88. data/lib/action_dispatch/middleware/debug_view.rb +66 -0
  89. data/lib/action_dispatch/middleware/exception_wrapper.rb +75 -30
  90. data/lib/action_dispatch/middleware/flash.rb +1 -1
  91. data/lib/action_dispatch/middleware/host_authorization.rb +141 -0
  92. data/lib/action_dispatch/middleware/public_exceptions.rb +6 -3
  93. data/lib/action_dispatch/middleware/remote_ip.rb +14 -16
  94. data/lib/action_dispatch/middleware/request_id.rb +5 -6
  95. data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -3
  96. data/lib/action_dispatch/middleware/session/cookie_store.rb +3 -9
  97. data/lib/action_dispatch/middleware/show_exceptions.rb +3 -2
  98. data/lib/action_dispatch/middleware/ssl.rb +20 -15
  99. data/lib/action_dispatch/middleware/stack.rb +56 -2
  100. data/lib/action_dispatch/middleware/static.rb +153 -93
  101. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  102. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  103. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  104. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +3 -1
  105. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  106. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +4 -2
  107. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +45 -35
  108. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -0
  109. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -0
  110. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +23 -4
  111. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
  112. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +6 -3
  113. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +4 -1
  114. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +104 -8
  115. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  116. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  117. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +2 -2
  118. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -1
  119. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +2 -2
  120. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  121. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +24 -1
  122. data/lib/action_dispatch/railtie.rb +8 -2
  123. data/lib/action_dispatch/request/session.rb +11 -10
  124. data/lib/action_dispatch/request/utils.rb +26 -2
  125. data/lib/action_dispatch/routing/inspector.rb +100 -52
  126. data/lib/action_dispatch/routing/mapper.rb +155 -103
  127. data/lib/action_dispatch/routing/polymorphic_routes.rb +13 -15
  128. data/lib/action_dispatch/routing/redirection.rb +4 -4
  129. data/lib/action_dispatch/routing/route_set.rb +71 -69
  130. data/lib/action_dispatch/routing/url_for.rb +2 -2
  131. data/lib/action_dispatch/routing.rb +21 -20
  132. data/lib/action_dispatch/system_test_case.rb +54 -11
  133. data/lib/action_dispatch/system_testing/browser.rb +53 -16
  134. data/lib/action_dispatch/system_testing/driver.rb +11 -3
  135. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +49 -7
  136. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +8 -10
  137. data/lib/action_dispatch/testing/assertion_response.rb +0 -1
  138. data/lib/action_dispatch/testing/assertions/response.rb +4 -7
  139. data/lib/action_dispatch/testing/assertions/routing.rb +20 -8
  140. data/lib/action_dispatch/testing/assertions.rb +1 -1
  141. data/lib/action_dispatch/testing/integration.rb +60 -28
  142. data/lib/action_dispatch/testing/request_encoder.rb +2 -2
  143. data/lib/action_dispatch/testing/test_process.rb +29 -4
  144. data/lib/action_dispatch/testing/test_request.rb +3 -3
  145. data/lib/action_dispatch/testing/test_response.rb +4 -32
  146. data/lib/action_dispatch.rb +9 -3
  147. data/lib/action_pack/gem_version.rb +4 -4
  148. data/lib/action_pack.rb +1 -1
  149. metadata +35 -23
  150. data/lib/action_controller/metal/force_ssl.rb +0 -99
  151. data/lib/action_dispatch/http/parameter_filter.rb +0 -86
  152. data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
  153. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -49
  154. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -120
  155. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +0 -26
@@ -12,7 +12,7 @@ module ActionController
12
12
  #
13
13
  # An API Controller is different from a normal controller in the sense that
14
14
  # by default it doesn't include a number of features that are usually required
15
- # by browser access only: layouts and templates rendering, cookies, sessions,
15
+ # by browser access only: layouts and templates rendering,
16
16
  # flash, assets, and so on. This makes the entire controller stack thinner,
17
17
  # suitable for API applications. It doesn't mean you won't have such
18
18
  # features if you need them: they're all available for you to include in
@@ -93,7 +93,7 @@ module ActionController
93
93
  # the ones passed as arguments:
94
94
  #
95
95
  # class MyAPIBaseController < ActionController::Metal
96
- # ActionController::API.without_modules(:ForceSSL, :UrlFor).each do |left|
96
+ # ActionController::API.without_modules(:UrlFor).each do |left|
97
97
  # include left
98
98
  # end
99
99
  # end
@@ -120,8 +120,9 @@ module ActionController
120
120
  BasicImplicitRender,
121
121
  StrongParameters,
122
122
 
123
- ForceSSL,
124
123
  DataStreaming,
124
+ DefaultHeaders,
125
+ Logging,
125
126
 
126
127
  # Before callbacks should also be executed as early as possible, so
127
128
  # also include them at the bottom.
@@ -78,7 +78,7 @@ module ActionController
78
78
  #
79
79
  # You can retrieve it again through the same hash:
80
80
  #
81
- # Hello #{session[:person]}
81
+ # "Hello #{session[:person]}"
82
82
  #
83
83
  # For removing objects from the session, you can either assign a single key to +nil+:
84
84
  #
@@ -226,12 +226,14 @@ module ActionController
226
226
  FormBuilder,
227
227
  RequestForgeryProtection,
228
228
  ContentSecurityPolicy,
229
- ForceSSL,
229
+ PermissionsPolicy,
230
230
  Streaming,
231
231
  DataStreaming,
232
232
  HttpAuthentication::Basic::ControllerMethods,
233
233
  HttpAuthentication::Digest::ControllerMethods,
234
234
  HttpAuthentication::Token::ControllerMethods,
235
+ DefaultHeaders,
236
+ Logging,
235
237
 
236
238
  # Before callbacks should also be executed as early as possible, so
237
239
  # also include them at the bottom.
@@ -260,15 +262,10 @@ module ActionController
260
262
  @_view_renderer @_lookup_context @_routes @_view_runtime @_db_runtime @_helper_proxy
261
263
  )
262
264
 
263
- def _protected_ivars # :nodoc:
265
+ def _protected_ivars
264
266
  PROTECTED_IVARS
265
267
  end
266
-
267
- def self.make_response!(request)
268
- ActionDispatch::Response.create.tap do |res|
269
- res.request = request
270
- end
271
- end
268
+ private :_protected_ivars
272
269
 
273
270
  ActiveSupport.run_load_hooks(:action_controller_base, self)
274
271
  ActiveSupport.run_load_hooks(:action_controller, self)
@@ -22,7 +22,6 @@ module ActionController
22
22
  # config.action_controller.cache_store = :mem_cache_store, Memcached::Rails.new('localhost:11211')
23
23
  # config.action_controller.cache_store = MyOwnStore.new('parameter')
24
24
  module Caching
25
- extend ActiveSupport::Autoload
26
25
  extend ActiveSupport::Concern
27
26
 
28
27
  included do
@@ -30,7 +29,6 @@ module ActionController
30
29
  end
31
30
 
32
31
  private
33
-
34
32
  def instrument_payload(key)
35
33
  {
36
34
  controller: controller_name,
@@ -40,7 +38,7 @@ module ActionController
40
38
  end
41
39
 
42
40
  def instrument_name
43
- "action_controller".freeze
41
+ "action_controller"
44
42
  end
45
43
  end
46
44
  end
@@ -11,6 +11,7 @@ module ActionController
11
11
  params = payload[:params].except(*INTERNAL_PARAMS)
12
12
  format = payload[:format]
13
13
  format = format.to_s.upcase if format.is_a?(Symbol)
14
+ format = "*/*" if format.nil?
14
15
 
15
16
  info "Processing by #{payload[:controller]}##{payload[:action]} as #{format}"
16
17
  info " Parameters: #{params.inspect}" unless params.empty?
@@ -18,16 +19,18 @@ module ActionController
18
19
 
19
20
  def process_action(event)
20
21
  info do
21
- payload = event.payload
22
+ payload = event.payload
22
23
  additions = ActionController::Base.log_process_action(payload)
23
-
24
24
  status = payload[:status]
25
- if status.nil? && payload[:exception].present?
26
- exception_class_name = payload[:exception].first
25
+
26
+ if status.nil? && (exception_class_name = payload[:exception]&.first)
27
27
  status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
28
28
  end
29
- message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms".dup
30
- message << " (#{additions.join(" | ".freeze)})" unless additions.empty?
29
+
30
+ additions << "Allocations: #{event.allocations}"
31
+
32
+ message = +"Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms"
33
+ message << " (#{additions.join(" | ")})"
31
34
  message << "\n\n" if defined?(Rails.env) && Rails.env.development?
32
35
 
33
36
  message
@@ -53,7 +56,7 @@ module ActionController
53
56
  def unpermitted_parameters(event)
54
57
  debug do
55
58
  unpermitted_keys = event.payload[:keys]
56
- "Unpermitted parameter#{'s' if unpermitted_keys.size > 1}: #{unpermitted_keys.map { |e| ":#{e}" }.join(", ")}"
59
+ color("Unpermitted parameter#{'s' if unpermitted_keys.size > 1}: #{unpermitted_keys.map { |e| ":#{e}" }.join(", ")}", RED)
57
60
  end
58
61
  end
59
62
 
@@ -6,7 +6,7 @@ module ActionController
6
6
  super.tap { default_render unless performed? }
7
7
  end
8
8
 
9
- def default_render(*args)
9
+ def default_render
10
10
  head :no_content
11
11
  end
12
12
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/hash/keys"
3
+ require "active_support/core_ext/object/try"
4
+ require "active_support/core_ext/integer/time"
4
5
 
5
6
  module ActionController
6
7
  module ConditionalGet
@@ -19,12 +20,12 @@ module ActionController
19
20
  # of cached pages.
20
21
  #
21
22
  # class InvoicesController < ApplicationController
22
- # etag { current_user.try :id }
23
+ # etag { current_user&.id }
23
24
  #
24
25
  # def show
25
26
  # # Etag will differ even for the same invoice when it's viewed by a different current_user
26
27
  # @invoice = Invoice.find(params[:id])
27
- # fresh_when(@invoice)
28
+ # fresh_when etag: @invoice
28
29
  # end
29
30
  # end
30
31
  def etag(&etagger)
@@ -181,7 +182,7 @@ module ActionController
181
182
  #
182
183
  # You can also pass an object that responds to +maximum+, such as a
183
184
  # collection of active records. In this case +last_modified+ will be set by
184
- # calling +maximum(:updated_at)+ on the collection (the timestamp of the
185
+ # calling <tt>maximum(:updated_at)</tt> on the collection (the timestamp of the
185
186
  # most recently updated record) and the +etag+ by passing the object itself.
186
187
  #
187
188
  # def index
@@ -230,12 +231,25 @@ module ActionController
230
231
  # This method will overwrite an existing Cache-Control header.
231
232
  # See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
232
233
  #
234
+ # HTTP Cache-Control Extensions for Stale Content. See https://tools.ietf.org/html/rfc5861
235
+ # It helps to cache an asset and serve it while is being revalidated and/or returning with an error.
236
+ #
237
+ # expires_in 3.hours, public: true, stale_while_revalidate: 60.seconds
238
+ # expires_in 3.hours, public: true, stale_while_revalidate: 60.seconds, stale_if_error: 5.minutes
239
+ #
240
+ # HTTP Cache-Control Extensions other values: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
241
+ # Any additional key-value pairs are concatenated onto the `Cache-Control` header in the response:
242
+ #
243
+ # expires_in 3.hours, public: true, "s-maxage": 3.hours, "no-transform": true
244
+ #
233
245
  # The method will also ensure an HTTP Date header for client compatibility.
234
246
  def expires_in(seconds, options = {})
235
247
  response.cache_control.merge!(
236
248
  max_age: seconds,
237
249
  public: options.delete(:public),
238
- must_revalidate: options.delete(:must_revalidate)
250
+ must_revalidate: options.delete(:must_revalidate),
251
+ stale_while_revalidate: options.delete(:stale_while_revalidate),
252
+ stale_if_error: options.delete(:stale_if_error),
239
253
  )
240
254
  options.delete(:private)
241
255
 
@@ -36,7 +36,6 @@ module ActionController #:nodoc:
36
36
  end
37
37
 
38
38
  private
39
-
40
39
  def content_security_policy?
41
40
  request.content_security_policy
42
41
  end
@@ -46,7 +45,7 @@ module ActionController #:nodoc:
46
45
  end
47
46
 
48
47
  def current_content_security_policy
49
- request.content_security_policy.try(:clone) || ActionDispatch::ContentSecurityPolicy.new
48
+ request.content_security_policy&.clone || ActionDispatch::ContentSecurityPolicy.new
50
49
  end
51
50
  end
52
51
  end
@@ -9,7 +9,9 @@ module ActionController #:nodoc:
9
9
  end
10
10
 
11
11
  private
12
- def cookies
12
+ # The cookies for the current request. See ActionDispatch::Cookies for
13
+ # more information.
14
+ def cookies # :doc:
13
15
  request.cookie_jar
14
16
  end
15
17
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "action_controller/metal/exceptions"
4
+ require "action_dispatch/http/content_disposition"
4
5
 
5
6
  module ActionController #:nodoc:
6
7
  # Methods for sending arbitrary data and for streaming files to the browser,
@@ -10,8 +11,8 @@ module ActionController #:nodoc:
10
11
 
11
12
  include ActionController::Rendering
12
13
 
13
- DEFAULT_SEND_FILE_TYPE = "application/octet-stream".freeze #:nodoc:
14
- DEFAULT_SEND_FILE_DISPOSITION = "attachment".freeze #:nodoc:
14
+ DEFAULT_SEND_FILE_TYPE = "application/octet-stream" #:nodoc:
15
+ DEFAULT_SEND_FILE_DISPOSITION = "attachment" #:nodoc:
15
16
 
16
17
  private
17
18
  # Sends the file. This uses a server-appropriate method (such as X-Sendfile)
@@ -52,7 +53,7 @@ module ActionController #:nodoc:
52
53
  #
53
54
  # Show a 404 page in the browser:
54
55
  #
55
- # send_file '/path/to/404.html', type: 'text/html; charset=utf-8', status: 404
56
+ # send_file '/path/to/404.html', type: 'text/html; charset=utf-8', disposition: 'inline', status: 404
56
57
  #
57
58
  # Read about the other Content-* HTTP headers if you'd like to
58
59
  # provide the user with more information (such as Content-Description) in
@@ -132,10 +133,8 @@ module ActionController #:nodoc:
132
133
  end
133
134
 
134
135
  disposition = options.fetch(:disposition, DEFAULT_SEND_FILE_DISPOSITION)
135
- unless disposition.nil?
136
- disposition = disposition.to_s
137
- disposition += %(; filename="#{options[:filename]}") if options[:filename]
138
- headers["Content-Disposition"] = disposition
136
+ if disposition
137
+ headers["Content-Disposition"] = ActionDispatch::Http::ContentDisposition.format(disposition: disposition, filename: options[:filename])
139
138
  end
140
139
 
141
140
  headers["Content-Transfer-Encoding"] = "binary"
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionController
4
+ # Allows configuring default headers that will be automatically merged into
5
+ # each response.
6
+ module DefaultHeaders
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+ def make_response!(request)
11
+ ActionDispatch::Response.create.tap do |res|
12
+ res.request = request
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -26,10 +26,8 @@ module ActionController
26
26
  included do
27
27
  class_attribute :etag_with_template_digest, default: true
28
28
 
29
- ActiveSupport.on_load :action_view, yield: true do
30
- etag do |options|
31
- determine_template_etag(options) if etag_with_template_digest
32
- end
29
+ etag do |options|
30
+ determine_template_etag(options) if etag_with_template_digest
33
31
  end
34
32
  end
35
33
 
@@ -46,12 +44,12 @@ module ActionController
46
44
  # template digest from the ETag.
47
45
  def pick_template_for_etag(options)
48
46
  unless options[:template] == false
49
- options[:template] || "#{controller_path}/#{action_name}"
47
+ options[:template] || lookup_context.find_all(action_name, _prefixes).first&.virtual_path
50
48
  end
51
49
  end
52
50
 
53
51
  def lookup_and_digest_template(template)
54
- ActionView::Digestor.digest name: template, finder: lookup_context
52
+ ActionView::Digestor.digest name: template, format: nil, finder: lookup_context
55
53
  end
56
54
  end
57
55
  end
@@ -22,12 +22,45 @@ module ActionController
22
22
  end
23
23
  end
24
24
 
25
- class ActionController::UrlGenerationError < ActionControllerError #:nodoc:
25
+ class UrlGenerationError < ActionControllerError #:nodoc:
26
+ attr_reader :routes, :route_name, :method_name
27
+
28
+ def initialize(message, routes = nil, route_name = nil, method_name = nil)
29
+ @routes = routes
30
+ @route_name = route_name
31
+ @method_name = method_name
32
+
33
+ super(message)
34
+ end
35
+
36
+ class Correction
37
+ def initialize(error)
38
+ @error = error
39
+ end
40
+
41
+ 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
+ []
51
+ end
52
+ end
53
+ 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
26
59
  end
27
60
 
28
61
  class MethodNotAllowed < ActionControllerError #:nodoc:
29
62
  def initialize(*allowed_methods)
30
- super("Only #{allowed_methods.to_sentence(locale: :en)} requests are allowed.")
63
+ super("Only #{allowed_methods.to_sentence} requests are allowed.")
31
64
  end
32
65
  end
33
66
 
@@ -50,4 +83,25 @@ module ActionController
50
83
 
51
84
  class UnknownFormat < ActionControllerError #:nodoc:
52
85
  end
86
+
87
+ # Raised when a nested respond_to is triggered and the content types of each
88
+ # are incompatible. For example:
89
+ #
90
+ # respond_to do |outer_type|
91
+ # outer_type.js do
92
+ # respond_to do |inner_type|
93
+ # inner_type.html { render body: "HTML" }
94
+ # end
95
+ # end
96
+ # end
97
+ class RespondToMismatchError < ActionControllerError
98
+ DEFAULT_MESSAGE = "respond_to was called multiple times and matched with conflicting formats in this action. Please note that you may only call respond_to and match on a single format per action."
99
+
100
+ def initialize(message = nil)
101
+ super(message || DEFAULT_MESSAGE)
102
+ end
103
+ end
104
+
105
+ class MissingExactTemplate < UnknownFormat #:nodoc:
106
+ end
53
107
  end
@@ -36,7 +36,7 @@ module ActionController #:nodoc:
36
36
  define_method(type) do
37
37
  request.flash[type]
38
38
  end
39
- helper_method type
39
+ helper_method(type) if respond_to?(:helper_method)
40
40
 
41
41
  self._flash_types += [type]
42
42
  end
@@ -44,18 +44,18 @@ module ActionController #:nodoc:
44
44
  end
45
45
 
46
46
  private
47
- def redirect_to(options = {}, response_status_and_flash = {}) #:doc:
47
+ def redirect_to(options = {}, response_options_and_flash = {}) #:doc:
48
48
  self.class._flash_types.each do |flash_type|
49
- if type = response_status_and_flash.delete(flash_type)
49
+ if type = response_options_and_flash.delete(flash_type)
50
50
  flash[flash_type] = type
51
51
  end
52
52
  end
53
53
 
54
- if other_flashes = response_status_and_flash.delete(:flash)
54
+ if other_flashes = response_options_and_flash.delete(:flash)
55
55
  flash.update(other_flashes)
56
56
  end
57
57
 
58
- super(options, response_status_and_flash)
58
+ super(options, response_options_and_flash)
59
59
  end
60
60
  end
61
61
  end
@@ -29,19 +29,22 @@ module ActionController
29
29
  content_type = options.delete(:content_type)
30
30
 
31
31
  options.each do |key, value|
32
- headers[key.to_s.dasherize.split("-").each { |v| v[0] = v[0].chr.upcase }.join("-")] = value.to_s
32
+ headers[key.to_s.split(/[-_]/).each { |v| v[0] = v[0].upcase }.join("-")] = value.to_s
33
33
  end
34
34
 
35
35
  self.status = status
36
36
  self.location = url_for(location) if location
37
37
 
38
- self.response_body = ""
39
-
40
38
  if include_content?(response_code)
41
- self.content_type = content_type || (Mime[formats.first] if formats)
39
+ unless self.media_type
40
+ self.content_type = content_type || (Mime[formats.first] if formats) || Mime[:html]
41
+ end
42
+
42
43
  response.charset = false
43
44
  end
44
45
 
46
+ self.response_body = ""
47
+
45
48
  true
46
49
  end
47
50
 
@@ -11,7 +11,12 @@ module ActionController
11
11
  #
12
12
  # In previous versions of \Rails the controller will include a helper which
13
13
  # matches the name of the controller, e.g., <tt>MyController</tt> will automatically
14
- # include <tt>MyHelper</tt>. To return old behavior set +config.action_controller.include_all_helpers+ to +false+.
14
+ # include <tt>MyHelper</tt>. You can revert to the old behavior with the following:
15
+ #
16
+ # # config/application.rb
17
+ # class Application < Rails::Application
18
+ # config.action_controller.include_all_helpers = false
19
+ # end
15
20
  #
16
21
  # Additional helpers can be specified using the +helper+ class method in ActionController::Base or any
17
22
  # controller which inherits from it.
@@ -34,7 +39,7 @@ module ActionController
34
39
  # end
35
40
  # end
36
41
  #
37
- # Then, in any view rendered by <tt>EventController</tt>, the <tt>format_time</tt> method can be called:
42
+ # Then, in any view rendered by <tt>EventsController</tt>, the <tt>format_time</tt> method can be called:
38
43
  #
39
44
  # <% @events.each do |event| -%>
40
45
  # <p>
@@ -73,9 +78,14 @@ module ActionController
73
78
  end
74
79
 
75
80
  # Provides a proxy to access helper methods from outside the view.
81
+ #
82
+ # Note that the proxy is rendered under a different view context.
83
+ # This may cause incorrect behaviour with capture methods. Consider
84
+ # using {helper}[rdoc-ref:AbstractController::Helpers::ClassMethods#helper]
85
+ # instead when using +capture+.
76
86
  def helpers
77
87
  @helper_proxy ||= begin
78
- proxy = ActionView::Base.new
88
+ proxy = ActionView::Base.empty
79
89
  proxy.config = config.inheritable_copy
80
90
  proxy.extend(_helpers)
81
91
  end
@@ -100,8 +110,7 @@ module ActionController
100
110
  # # => ["application", "chart", "rubygems"]
101
111
  def all_helpers_from_path(path)
102
112
  helpers = Array(path).flat_map do |_path|
103
- extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/
104
- names = Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1'.freeze) }
113
+ names = Dir["#{_path}/**/*_helper.rb"].map { |file| file[_path.to_s.size + 1..-"_helper.rb".size - 1] }
105
114
  names.sort!
106
115
  end
107
116
  helpers.uniq!
@@ -56,8 +56,9 @@ module ActionController
56
56
  # In your integration tests, you can do something like this:
57
57
  #
58
58
  # def test_access_granted_from_xml
59
- # @request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
60
- # get "/notes/1.xml"
59
+ # authorization = ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
60
+ #
61
+ # get "/notes/1.xml", headers: { 'HTTP_AUTHORIZATION' => authorization }
61
62
  #
62
63
  # assert_equal 200, status
63
64
  # end
@@ -68,21 +69,22 @@ module ActionController
68
69
  extend ActiveSupport::Concern
69
70
 
70
71
  module ClassMethods
71
- def http_basic_authenticate_with(options = {})
72
- before_action(options.except(:name, :password, :realm)) do
73
- authenticate_or_request_with_http_basic(options[:realm] || "Application") do |name, password|
74
- # This comparison uses & so that it doesn't short circuit and
75
- # uses `secure_compare` so that length information
76
- # isn't leaked.
77
- ActiveSupport::SecurityUtils.secure_compare(name, options[:name]) &
78
- ActiveSupport::SecurityUtils.secure_compare(password, options[:password])
79
- end
80
- end
72
+ def http_basic_authenticate_with(name:, password:, realm: nil, **options)
73
+ before_action(options) { http_basic_authenticate_or_request_with name: name, password: password, realm: realm }
74
+ end
75
+ end
76
+
77
+ def http_basic_authenticate_or_request_with(name:, password:, realm: nil, message: nil)
78
+ authenticate_or_request_with_http_basic(realm, message) do |given_name, given_password|
79
+ # This comparison uses & so that it doesn't short circuit and
80
+ # uses `secure_compare` so that length information isn't leaked.
81
+ ActiveSupport::SecurityUtils.secure_compare(given_name, name) &
82
+ ActiveSupport::SecurityUtils.secure_compare(given_password, password)
81
83
  end
82
84
  end
83
85
 
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)
86
+ def authenticate_or_request_with_http_basic(realm = nil, message = nil, &login_procedure)
87
+ authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm || "Application", message)
86
88
  end
87
89
 
88
90
  def authenticate_with_http_basic(&login_procedure)
@@ -126,7 +128,7 @@ module ActionController
126
128
 
127
129
  def authentication_request(controller, realm, message)
128
130
  message ||= "HTTP Basic: Access denied.\n"
129
- controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.tr('"'.freeze, "".freeze)}")
131
+ controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.tr('"', "")}")
130
132
  controller.status = 401
131
133
  controller.response_body = message
132
134
  end
@@ -136,7 +138,7 @@ module ActionController
136
138
  #
137
139
  # === Simple \Digest example
138
140
  #
139
- # require 'digest/md5'
141
+ # require "digest/md5"
140
142
  # class PostsController < ApplicationController
141
143
  # REALM = "SuperSecret"
142
144
  # USERS = {"dhh" => "secret", #plain text password
@@ -389,10 +391,9 @@ module ActionController
389
391
  # In your integration tests, you can do something like this:
390
392
  #
391
393
  # def test_access_granted_from_xml
392
- # get(
393
- # "/notes/1.xml", nil,
394
- # 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Token.encode_credentials(users(:dhh).token)
395
- # )
394
+ # authorization = ActionController::HttpAuthentication::Token.encode_credentials(users(:dhh).token)
395
+ #
396
+ # get "/notes/1.xml", headers: { 'HTTP_AUTHORIZATION' => authorization }
396
397
  #
397
398
  # assert_equal 200, status
398
399
  # end
@@ -474,7 +475,7 @@ module ActionController
474
475
 
475
476
  # This removes the <tt>"</tt> characters wrapping the value.
476
477
  def rewrite_param_values(array_params)
477
- array_params.each { |param| (param[1] || "".dup).gsub! %r/^"|"$/, "" }
478
+ array_params.each { |param| (param[1] || +"").gsub! %r/^"|"$/, "" }
478
479
  end
479
480
 
480
481
  # This method takes an authorization body and splits up the key-value
@@ -483,7 +484,7 @@ module ActionController
483
484
  def raw_params(auth)
484
485
  _raw_params = auth.sub(TOKEN_REGEX, "").split(/\s*#{AUTHN_PAIR_DELIMITERS}\s*/)
485
486
 
486
- if !(_raw_params.first =~ %r{\A#{TOKEN_KEY}})
487
+ if !_raw_params.first&.start_with?(TOKEN_KEY)
487
488
  _raw_params[0] = "#{TOKEN_KEY}#{_raw_params.first}"
488
489
  end
489
490
 
@@ -511,7 +512,7 @@ module ActionController
511
512
  # Returns nothing.
512
513
  def authentication_request(controller, realm, message = nil)
513
514
  message ||= "HTTP Token: Access denied.\n"
514
- controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.tr('"'.freeze, "".freeze)}")
515
+ controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.tr('"', "")}")
515
516
  controller.__send__ :render, plain: message, status: :unauthorized
516
517
  end
517
518
  end
@@ -22,7 +22,7 @@ module ActionController
22
22
  # Third, if we DON'T find a template AND the request is a page load in a web
23
23
  # browser (technically, a non-XHR GET request for an HTML response) where
24
24
  # you reasonably expect to have rendered a template, then we raise
25
- # <tt>ActionView::UnknownFormat</tt> with an explanation.
25
+ # <tt>ActionController::MissingExactTemplate</tt> with an explanation.
26
26
  #
27
27
  # Finally, if we DON'T find a template AND the request isn't a browser page
28
28
  # load, then we implicitly respond with <tt>204 No Content</tt>.
@@ -30,9 +30,9 @@ module ActionController
30
30
  # :stopdoc:
31
31
  include BasicImplicitRender
32
32
 
33
- def default_render(*args)
33
+ def default_render
34
34
  if template_exists?(action_name.to_s, _prefixes, variants: request.variant)
35
- render(*args)
35
+ render
36
36
  elsif any_templates?(action_name.to_s, _prefixes)
37
37
  message = "#{self.class.name}\##{action_name} is missing a template " \
38
38
  "for this request format and variant.\n" \
@@ -41,18 +41,8 @@ module ActionController
41
41
 
42
42
  raise ActionController::UnknownFormat, message
43
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
44
+ message = "#{self.class.name}\##{action_name} is missing a template for request formats: #{request.formats.map(&:to_s).join(',')}"
45
+ raise ActionController::MissingExactTemplate, message
56
46
  else
57
47
  logger.info "No template found for #{self.class.name}\##{action_name}, rendering head :no_content" if logger
58
48
  super