actionpack 5.2.7.1 → 6.1.7.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (155) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +427 -338
  3. data/MIT-LICENSE +1 -2
  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 +5 -4
  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 +25 -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 +26 -7
  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 +168 -59
  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 +32 -28
  54. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  55. data/lib/action_dispatch/http/content_security_policy.rb +34 -18
  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 +150 -123
  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 +170 -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 +13 -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 +60 -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 +32 -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 +3 -3
  148. data/lib/action_pack.rb +1 -1
  149. metadata +36 -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!
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "base64"
4
4
  require "active_support/security_utils"
5
+ require "active_support/core_ext/array/access"
5
6
 
6
7
  module ActionController
7
8
  # Makes it dead easy to do HTTP Basic, Digest and Token authentication.
@@ -56,8 +57,9 @@ module ActionController
56
57
  # In your integration tests, you can do something like this:
57
58
  #
58
59
  # 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"
60
+ # authorization = ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
61
+ #
62
+ # get "/notes/1.xml", headers: { 'HTTP_AUTHORIZATION' => authorization }
61
63
  #
62
64
  # assert_equal 200, status
63
65
  # end
@@ -68,21 +70,22 @@ module ActionController
68
70
  extend ActiveSupport::Concern
69
71
 
70
72
  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
73
+ def http_basic_authenticate_with(name:, password:, realm: nil, **options)
74
+ before_action(options) { http_basic_authenticate_or_request_with name: name, password: password, realm: realm }
75
+ end
76
+ end
77
+
78
+ def http_basic_authenticate_or_request_with(name:, password:, realm: nil, message: nil)
79
+ authenticate_or_request_with_http_basic(realm, message) do |given_name, given_password|
80
+ # This comparison uses & so that it doesn't short circuit and
81
+ # 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)
81
84
  end
82
85
  end
83
86
 
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)
87
+ def authenticate_or_request_with_http_basic(realm = nil, message = nil, &login_procedure)
88
+ authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm || "Application", message)
86
89
  end
87
90
 
88
91
  def authenticate_with_http_basic(&login_procedure)
@@ -126,7 +129,7 @@ module ActionController
126
129
 
127
130
  def authentication_request(controller, realm, message)
128
131
  message ||= "HTTP Basic: Access denied.\n"
129
- controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.tr('"'.freeze, "".freeze)}")
132
+ controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.tr('"', "")}")
130
133
  controller.status = 401
131
134
  controller.response_body = message
132
135
  end
@@ -136,7 +139,7 @@ module ActionController
136
139
  #
137
140
  # === Simple \Digest example
138
141
  #
139
- # require 'digest/md5'
142
+ # require "digest/md5"
140
143
  # class PostsController < ApplicationController
141
144
  # REALM = "SuperSecret"
142
145
  # USERS = {"dhh" => "secret", #plain text password
@@ -389,10 +392,9 @@ module ActionController
389
392
  # In your integration tests, you can do something like this:
390
393
  #
391
394
  # 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
- # )
395
+ # authorization = ActionController::HttpAuthentication::Token.encode_credentials(users(:dhh).token)
396
+ #
397
+ # get "/notes/1.xml", headers: { 'HTTP_AUTHORIZATION' => authorization }
396
398
  #
397
399
  # assert_equal 200, status
398
400
  # end
@@ -474,7 +476,7 @@ module ActionController
474
476
 
475
477
  # This removes the <tt>"</tt> characters wrapping the value.
476
478
  def rewrite_param_values(array_params)
477
- array_params.each { |param| (param[1] || "".dup).gsub! %r/^"|"$/, "" }
479
+ array_params.each { |param| (param[1] || +"").gsub! %r/^"|"$/, "" }
478
480
  end
479
481
 
480
482
  # This method takes an authorization body and splits up the key-value
@@ -483,7 +485,7 @@ module ActionController
483
485
  def raw_params(auth)
484
486
  _raw_params = auth.sub(TOKEN_REGEX, "").split(/\s*#{AUTHN_PAIR_DELIMITERS}\s*/)
485
487
 
486
- if !(_raw_params.first =~ %r{\A#{TOKEN_KEY}})
488
+ if !_raw_params.first&.start_with?(TOKEN_KEY)
487
489
  _raw_params[0] = "#{TOKEN_KEY}#{_raw_params.first}"
488
490
  end
489
491
 
@@ -511,7 +513,7 @@ module ActionController
511
513
  # Returns nothing.
512
514
  def authentication_request(controller, realm, message = nil)
513
515
  message ||= "HTTP Token: Access denied.\n"
514
- controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.tr('"'.freeze, "".freeze)}")
516
+ controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.tr('"', "")}")
515
517
  controller.__send__ :render, plain: message, status: :unauthorized
516
518
  end
517
519
  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