actionpack 5.2.1 → 7.0.2.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 (167) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +264 -220
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -6
  5. data/lib/abstract_controller/asset_paths.rb +1 -1
  6. data/lib/abstract_controller/base.rb +24 -4
  7. data/lib/abstract_controller/caching/fragments.rb +8 -24
  8. data/lib/abstract_controller/caching.rb +2 -2
  9. data/lib/abstract_controller/callbacks.rb +34 -8
  10. data/lib/abstract_controller/collector.rb +5 -4
  11. data/lib/abstract_controller/error.rb +1 -1
  12. data/lib/abstract_controller/helpers.rb +107 -90
  13. data/lib/abstract_controller/logger.rb +1 -1
  14. data/lib/abstract_controller/railties/routes_helpers.rb +19 -1
  15. data/lib/abstract_controller/rendering.rb +9 -9
  16. data/lib/abstract_controller/translation.rb +12 -5
  17. data/lib/abstract_controller/url_for.rb +4 -6
  18. data/lib/abstract_controller.rb +2 -0
  19. data/lib/action_controller/api.rb +5 -4
  20. data/lib/action_controller/base.rb +6 -9
  21. data/lib/action_controller/caching.rb +1 -3
  22. data/lib/action_controller/log_subscriber.rb +13 -9
  23. data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
  24. data/lib/action_controller/metal/conditional_get.rb +57 -6
  25. data/lib/action_controller/metal/content_security_policy.rb +2 -3
  26. data/lib/action_controller/metal/cookies.rb +4 -2
  27. data/lib/action_controller/metal/data_streaming.rb +9 -18
  28. data/lib/action_controller/metal/default_headers.rb +17 -0
  29. data/lib/action_controller/metal/etag_with_template_digest.rb +4 -6
  30. data/lib/action_controller/metal/exceptions.rb +55 -12
  31. data/lib/action_controller/metal/flash.rb +10 -6
  32. data/lib/action_controller/metal/head.rb +7 -4
  33. data/lib/action_controller/metal/helpers.rb +15 -6
  34. data/lib/action_controller/metal/http_authentication.rb +41 -39
  35. data/lib/action_controller/metal/implicit_render.rb +5 -15
  36. data/lib/action_controller/metal/instrumentation.rb +59 -55
  37. data/lib/action_controller/metal/live.rb +80 -33
  38. data/lib/action_controller/metal/logging.rb +20 -0
  39. data/lib/action_controller/metal/mime_responds.rb +22 -7
  40. data/lib/action_controller/metal/parameter_encoding.rb +35 -4
  41. data/lib/action_controller/metal/params_wrapper.rb +50 -31
  42. data/lib/action_controller/metal/permissions_policy.rb +46 -0
  43. data/lib/action_controller/metal/redirecting.rb +93 -23
  44. data/lib/action_controller/metal/renderers.rb +4 -4
  45. data/lib/action_controller/metal/rendering.rb +14 -9
  46. data/lib/action_controller/metal/request_forgery_protection.rb +160 -58
  47. data/lib/action_controller/metal/rescue.rb +2 -2
  48. data/lib/action_controller/metal/streaming.rb +1 -4
  49. data/lib/action_controller/metal/strong_parameters.rb +236 -88
  50. data/lib/action_controller/metal/testing.rb +9 -2
  51. data/lib/action_controller/metal/url_for.rb +1 -1
  52. data/lib/action_controller/metal.rb +16 -17
  53. data/lib/action_controller/railtie.rb +49 -6
  54. data/lib/action_controller/railties/helpers.rb +1 -1
  55. data/lib/action_controller/renderer.rb +37 -13
  56. data/lib/action_controller/template_assertions.rb +1 -1
  57. data/lib/action_controller/test_case.rb +98 -68
  58. data/lib/action_controller.rb +4 -5
  59. data/lib/action_dispatch/http/cache.rb +45 -32
  60. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  61. data/lib/action_dispatch/http/content_security_policy.rb +69 -56
  62. data/lib/action_dispatch/http/filter_parameters.rb +14 -8
  63. data/lib/action_dispatch/http/filter_redirect.rb +2 -3
  64. data/lib/action_dispatch/http/headers.rb +4 -4
  65. data/lib/action_dispatch/http/mime_negotiation.rb +44 -16
  66. data/lib/action_dispatch/http/mime_type.rb +47 -30
  67. data/lib/action_dispatch/http/parameters.rb +18 -27
  68. data/lib/action_dispatch/http/permissions_policy.rb +173 -0
  69. data/lib/action_dispatch/http/request.rb +49 -35
  70. data/lib/action_dispatch/http/response.rb +34 -26
  71. data/lib/action_dispatch/http/upload.rb +9 -1
  72. data/lib/action_dispatch/http/url.rb +86 -94
  73. data/lib/action_dispatch/journey/formatter.rb +55 -31
  74. data/lib/action_dispatch/journey/gtg/builder.rb +30 -46
  75. data/lib/action_dispatch/journey/gtg/simulator.rb +15 -8
  76. data/lib/action_dispatch/journey/gtg/transition_table.rb +78 -21
  77. data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
  78. data/lib/action_dispatch/journey/nodes/node.rb +83 -16
  79. data/lib/action_dispatch/journey/parser.rb +13 -13
  80. data/lib/action_dispatch/journey/parser.y +1 -1
  81. data/lib/action_dispatch/journey/path/pattern.rb +42 -34
  82. data/lib/action_dispatch/journey/route.rb +14 -31
  83. data/lib/action_dispatch/journey/router/utils.rb +16 -14
  84. data/lib/action_dispatch/journey/router.rb +27 -35
  85. data/lib/action_dispatch/journey/routes.rb +3 -5
  86. data/lib/action_dispatch/journey/scanner.rb +10 -4
  87. data/lib/action_dispatch/journey/visitors.rb +1 -4
  88. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  89. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  90. data/lib/action_dispatch/journey.rb +0 -2
  91. data/lib/action_dispatch/middleware/actionable_exceptions.rb +45 -0
  92. data/lib/action_dispatch/middleware/callbacks.rb +2 -4
  93. data/lib/action_dispatch/middleware/cookies.rb +136 -113
  94. data/lib/action_dispatch/middleware/debug_exceptions.rb +47 -68
  95. data/lib/action_dispatch/middleware/debug_locks.rb +8 -8
  96. data/lib/action_dispatch/middleware/debug_view.rb +66 -0
  97. data/lib/action_dispatch/middleware/exception_wrapper.rb +79 -30
  98. data/lib/action_dispatch/middleware/executor.rb +4 -1
  99. data/lib/action_dispatch/middleware/flash.rb +10 -12
  100. data/lib/action_dispatch/middleware/host_authorization.rb +159 -0
  101. data/lib/action_dispatch/middleware/public_exceptions.rb +6 -3
  102. data/lib/action_dispatch/middleware/remote_ip.rb +30 -20
  103. data/lib/action_dispatch/middleware/request_id.rb +5 -6
  104. data/lib/action_dispatch/middleware/server_timing.rb +33 -0
  105. data/lib/action_dispatch/middleware/session/abstract_store.rb +16 -3
  106. data/lib/action_dispatch/middleware/session/cache_store.rb +11 -6
  107. data/lib/action_dispatch/middleware/session/cookie_store.rb +24 -19
  108. data/lib/action_dispatch/middleware/show_exceptions.rb +20 -11
  109. data/lib/action_dispatch/middleware/ssl.rb +20 -15
  110. data/lib/action_dispatch/middleware/stack.rb +79 -7
  111. data/lib/action_dispatch/middleware/static.rb +150 -94
  112. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  113. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  114. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  115. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +6 -11
  116. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  117. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +4 -2
  118. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +46 -36
  119. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +8 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +7 -0
  121. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +25 -6
  122. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
  123. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +9 -6
  124. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +4 -1
  125. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +121 -15
  126. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  128. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +5 -5
  129. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +4 -4
  130. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +5 -5
  131. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
  132. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +16 -2
  133. data/lib/action_dispatch/railtie.rb +16 -4
  134. data/lib/action_dispatch/request/session.rb +59 -22
  135. data/lib/action_dispatch/request/utils.rb +28 -2
  136. data/lib/action_dispatch/routing/inspector.rb +102 -54
  137. data/lib/action_dispatch/routing/mapper.rb +184 -156
  138. data/lib/action_dispatch/routing/polymorphic_routes.rb +21 -19
  139. data/lib/action_dispatch/routing/redirection.rb +4 -6
  140. data/lib/action_dispatch/routing/route_set.rb +83 -73
  141. data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
  142. data/lib/action_dispatch/routing/url_for.rb +2 -3
  143. data/lib/action_dispatch/routing.rb +23 -22
  144. data/lib/action_dispatch/system_test_case.rb +65 -16
  145. data/lib/action_dispatch/system_testing/browser.rb +43 -16
  146. data/lib/action_dispatch/system_testing/driver.rb +42 -10
  147. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +58 -12
  148. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +3 -10
  149. data/lib/action_dispatch/testing/assertion_response.rb +0 -1
  150. data/lib/action_dispatch/testing/assertions/response.rb +4 -7
  151. data/lib/action_dispatch/testing/assertions/routing.rb +20 -8
  152. data/lib/action_dispatch/testing/assertions.rb +3 -6
  153. data/lib/action_dispatch/testing/integration.rb +61 -30
  154. data/lib/action_dispatch/testing/request_encoder.rb +2 -2
  155. data/lib/action_dispatch/testing/test_process.rb +8 -6
  156. data/lib/action_dispatch/testing/test_request.rb +3 -3
  157. data/lib/action_dispatch/testing/test_response.rb +4 -32
  158. data/lib/action_dispatch.rb +15 -7
  159. data/lib/action_pack/gem_version.rb +4 -4
  160. data/lib/action_pack.rb +1 -1
  161. metadata +44 -25
  162. data/lib/action_controller/metal/force_ssl.rb +0 -99
  163. data/lib/action_dispatch/http/parameter_filter.rb +0 -86
  164. data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
  165. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -49
  166. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -120
  167. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +0 -26
@@ -28,6 +28,7 @@ module AbstractController
28
28
  else
29
29
  _set_rendered_content_type rendered_format
30
30
  end
31
+ _set_vary_header
31
32
  self.response_body = rendered_body
32
33
  end
33
34
 
@@ -55,20 +56,16 @@ module AbstractController
55
56
  Mime[:text]
56
57
  end
57
58
 
58
- DEFAULT_PROTECTED_INSTANCE_VARIABLES = Set.new %i(
59
- @_action_name @_response_body @_formats @_prefixes
60
- )
59
+ DEFAULT_PROTECTED_INSTANCE_VARIABLES = %i(@_action_name @_response_body @_formats @_prefixes)
61
60
 
62
61
  # This method should return a hash with assigns.
63
62
  # You can overwrite this configuration per controller.
64
63
  def view_assigns
65
- protected_vars = _protected_ivars
66
- variables = instance_variables
64
+ variables = instance_variables - _protected_ivars
67
65
 
68
- variables.reject! { |s| protected_vars.include? s }
69
- variables.each_with_object({}) { |name, hash|
66
+ variables.each_with_object({}) do |name, hash|
70
67
  hash[name.slice(1, name.length)] = instance_variable_get(name)
71
- }
68
+ end
72
69
  end
73
70
 
74
71
  private
@@ -109,6 +106,9 @@ module AbstractController
109
106
  def _set_html_content_type # :nodoc:
110
107
  end
111
108
 
109
+ def _set_vary_header # :nodoc:
110
+ end
111
+
112
112
  def _set_rendered_content_type(format) # :nodoc:
113
113
  end
114
114
 
@@ -120,7 +120,7 @@ module AbstractController
120
120
  options
121
121
  end
122
122
 
123
- def _protected_ivars # :nodoc:
123
+ def _protected_ivars
124
124
  DEFAULT_PROTECTED_INSTANCE_VARIABLES
125
125
  end
126
126
  end
@@ -1,7 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/html_safe_translation"
4
+
3
5
  module AbstractController
4
6
  module Translation
7
+ mattr_accessor :raise_on_missing_translations, default: false
8
+
5
9
  # Delegates to <tt>I18n.translate</tt>. Also aliased as <tt>t</tt>.
6
10
  #
7
11
  # When the given key starts with a period, it will be scoped by the current
@@ -10,21 +14,24 @@ module AbstractController
10
14
  # <tt>I18n.translate("people.index.foo")</tt>. This makes it less repetitive
11
15
  # to translate many keys within the same controller / action and gives you a
12
16
  # simple framework for scoping them consistently.
13
- def translate(key, options = {})
14
- if key.to_s.first == "."
17
+ def translate(key, **options)
18
+ if key&.start_with?(".")
15
19
  path = controller_path.tr("/", ".")
16
20
  defaults = [:"#{path}#{key}"]
17
21
  defaults << options[:default] if options[:default]
18
22
  options[:default] = defaults.flatten
19
23
  key = "#{path}.#{action_name}#{key}"
20
24
  end
21
- I18n.translate(key, options)
25
+
26
+ i18n_raise = options.fetch(:raise, self.raise_on_missing_translations)
27
+
28
+ ActiveSupport::HtmlSafeTranslation.translate(key, **options, raise: i18n_raise)
22
29
  end
23
30
  alias :t :translate
24
31
 
25
32
  # Delegates to <tt>I18n.localize</tt>. Also aliased as <tt>l</tt>.
26
- def localize(*args)
27
- I18n.localize(*args)
33
+ def localize(object, **options)
34
+ I18n.localize(object, **options)
28
35
  end
29
36
  alias :l :localize
30
37
  end
@@ -22,12 +22,10 @@ module AbstractController
22
22
  end
23
23
 
24
24
  def action_methods
25
- @action_methods ||= begin
26
- if _routes
27
- super - _routes.named_routes.helper_names
28
- else
29
- super
30
- end
25
+ @action_methods ||= if _routes
26
+ super - _routes.named_routes.helper_names
27
+ else
28
+ super
31
29
  end
32
30
  end
33
31
  end
@@ -1,12 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "action_pack"
4
+ require "active_support"
4
5
  require "active_support/rails"
5
6
  require "active_support/i18n"
6
7
 
7
8
  module AbstractController
8
9
  extend ActiveSupport::Autoload
9
10
 
11
+ autoload :ActionNotFound, "abstract_controller/base"
10
12
  autoload :Base
11
13
  autoload :Caching
12
14
  autoload :Callbacks
@@ -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
@@ -37,7 +37,7 @@ module ActionController
37
37
  # == Renders
38
38
  #
39
39
  # The default API Controller stack includes all renderers, which means you
40
- # can use <tt>render :json</tt> and brothers freely in your controllers. Keep
40
+ # can use <tt>render :json</tt> and siblings freely in your controllers. Keep
41
41
  # in mind that templates are not going to be rendered, so you need to ensure
42
42
  # your controller is calling either <tt>render</tt> or <tt>redirect_to</tt> in
43
43
  # all actions, otherwise it will return 204 No Content.
@@ -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,12 +56,13 @@ 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
+ display_unpermitted_keys = unpermitted_keys.map { |e| ":#{e}" }.join(", ")
60
+ context = event.payload[:context].map { |k, v| "#{k}: #{v}" }.join(", ")
61
+ color("Unpermitted parameter#{'s' if unpermitted_keys.size > 1}: #{display_unpermitted_keys}. Context: { #{context} }", RED)
57
62
  end
58
63
  end
59
64
 
60
- %w(write_fragment read_fragment exist_fragment?
61
- expire_fragment expire_page write_page).each do |method|
65
+ %w(write_fragment read_fragment exist_fragment? expire_fragment).each do |method|
62
66
  class_eval <<-METHOD, __FILE__, __LINE__ + 1
63
67
  def #{method}(event)
64
68
  return unless logger.info? && ActionController::Base.enable_fragment_cache_logging
@@ -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)
@@ -56,6 +57,8 @@ module ActionController
56
57
  # 304 Not Modified response if last_modified <= If-Modified-Since.
57
58
  # * <tt>:public</tt> By default the Cache-Control header is private, set this to
58
59
  # +true+ if you want your application to be cacheable by other devices (proxy caches).
60
+ # * <tt>:cache_control</tt> When given will overwrite an existing Cache-Control header.
61
+ # See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
59
62
  # * <tt>:template</tt> By default, the template digest for the current
60
63
  # controller/action is included in ETags. If the action renders a
61
64
  # different template, you can include its digest instead. If the action
@@ -97,12 +100,22 @@ module ActionController
97
100
  # fresh_when(@article, public: true)
98
101
  # end
99
102
  #
103
+ # When overwriting Cache-Control header:
104
+ #
105
+ # def show
106
+ # @article = Article.find(params[:id])
107
+ # fresh_when(@article, public: true, cache_control: { no_cache: true })
108
+ # end
109
+ #
110
+ # This will set in the response Cache-Control = public, no-cache.
111
+ #
100
112
  # When rendering a different template than the default controller/action
101
113
  # style, you can indicate which digest to include in the ETag:
102
114
  #
103
115
  # before_action { fresh_when @article, template: 'widgets/show' }
104
116
  #
105
- def fresh_when(object = nil, etag: nil, weak_etag: nil, strong_etag: nil, last_modified: nil, public: false, template: nil)
117
+ def fresh_when(object = nil, etag: nil, weak_etag: nil, strong_etag: nil, last_modified: nil, public: false, cache_control: {}, template: nil)
118
+ response.cache_control.delete(:no_store)
106
119
  weak_etag ||= etag || object unless strong_etag
107
120
  last_modified ||= object.try(:updated_at) || object.try(:maximum, :updated_at)
108
121
 
@@ -116,6 +129,7 @@ module ActionController
116
129
 
117
130
  response.last_modified = last_modified if last_modified
118
131
  response.cache_control[:public] = true if public
132
+ response.cache_control.merge!(cache_control)
119
133
 
120
134
  head :not_modified if request.fresh?(response)
121
135
  end
@@ -146,6 +160,8 @@ module ActionController
146
160
  # 304 Not Modified response if last_modified <= If-Modified-Since.
147
161
  # * <tt>:public</tt> By default the Cache-Control header is private, set this to
148
162
  # +true+ if you want your application to be cacheable by other devices (proxy caches).
163
+ # * <tt>:cache_control</tt> When given will overwrite an existing Cache-Control header.
164
+ # See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
149
165
  # * <tt>:template</tt> By default, the template digest for the current
150
166
  # controller/action is included in ETags. If the action renders a
151
167
  # different template, you can include its digest instead. If the action
@@ -181,7 +197,7 @@ module ActionController
181
197
  #
182
198
  # You can also pass an object that responds to +maximum+, such as a
183
199
  # 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
200
+ # calling <tt>maximum(:updated_at)</tt> on the collection (the timestamp of the
185
201
  # most recently updated record) and the +etag+ by passing the object itself.
186
202
  #
187
203
  # def index
@@ -208,6 +224,21 @@ module ActionController
208
224
  # end
209
225
  # end
210
226
  #
227
+ # When overwriting Cache-Control header:
228
+ #
229
+ # def show
230
+ # @article = Article.find(params[:id])
231
+ #
232
+ # if stale?(@article, public: true, cache_control: { no_cache: true })
233
+ # @statistics = @articles.really_expensive_call
234
+ # respond_to do |format|
235
+ # # all the supported formats
236
+ # end
237
+ # end
238
+ # end
239
+ #
240
+ # This will set in the response Cache-Control = public, no-cache.
241
+ #
211
242
  # When rendering a different template than the default controller/action
212
243
  # style, you can indicate which digest to include in the ETag:
213
244
  #
@@ -230,12 +261,26 @@ module ActionController
230
261
  # This method will overwrite an existing Cache-Control header.
231
262
  # See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
232
263
  #
264
+ # HTTP Cache-Control Extensions for Stale Content. See https://tools.ietf.org/html/rfc5861
265
+ # It helps to cache an asset and serve it while is being revalidated and/or returning with an error.
266
+ #
267
+ # expires_in 3.hours, public: true, stale_while_revalidate: 60.seconds
268
+ # expires_in 3.hours, public: true, stale_while_revalidate: 60.seconds, stale_if_error: 5.minutes
269
+ #
270
+ # HTTP Cache-Control Extensions other values: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
271
+ # Any additional key-value pairs are concatenated onto the `Cache-Control` header in the response:
272
+ #
273
+ # expires_in 3.hours, public: true, "s-maxage": 3.hours, "no-transform": true
274
+ #
233
275
  # The method will also ensure an HTTP Date header for client compatibility.
234
276
  def expires_in(seconds, options = {})
277
+ response.cache_control.delete(:no_store)
235
278
  response.cache_control.merge!(
236
279
  max_age: seconds,
237
280
  public: options.delete(:public),
238
- must_revalidate: options.delete(:must_revalidate)
281
+ must_revalidate: options.delete(:must_revalidate),
282
+ stale_while_revalidate: options.delete(:stale_while_revalidate),
283
+ stale_if_error: options.delete(:stale_if_error),
239
284
  )
240
285
  options.delete(:private)
241
286
 
@@ -266,6 +311,12 @@ module ActionController
266
311
  public: public)
267
312
  end
268
313
 
314
+ # Sets an HTTP 1.1 Cache-Control header of <tt>no-store</tt>. This means the
315
+ # resource may not be stored in any cache.
316
+ def no_store
317
+ response.cache_control.replace(no_store: true)
318
+ end
319
+
269
320
  private
270
321
  def combine_etags(validator, options)
271
322
  [validator, *etaggers.map { |etagger| instance_exec(options, &etagger) }].compact
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ActionController #:nodoc:
3
+ module ActionController # :nodoc:
4
4
  module ContentSecurityPolicy
5
5
  # TODO: Documentation
6
6
  extend ActiveSupport::Concern
@@ -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
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ActionController #:nodoc:
3
+ module ActionController # :nodoc:
4
4
  module Cookies
5
5
  extend ActiveSupport::Concern
6
6
 
@@ -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,8 +1,9 @@
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
- module ActionController #:nodoc:
6
+ module ActionController # :nodoc:
6
7
  # Methods for sending arbitrary data and for streaming files to the browser,
7
8
  # instead of rendering.
8
9
  module DataStreaming
@@ -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
@@ -65,7 +66,7 @@ module ActionController #:nodoc:
65
66
  # https://www.mnot.net/cache_docs/ for an overview of web caching and
66
67
  # https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
67
68
  # for the Cache-Control header spec.
68
- def send_file(path, options = {}) #:doc:
69
+ def send_file(path, options = {}) # :doc:
69
70
  raise MissingFile, "Cannot read file #{path}" unless File.file?(path) && File.readable?(path)
70
71
 
71
72
  options[:filename] ||= File.basename(path) unless options[:url_based_filename]
@@ -105,7 +106,7 @@ module ActionController #:nodoc:
105
106
  # send_data image.data, type: image.content_type, disposition: 'inline'
106
107
  #
107
108
  # See +send_file+ for more information on HTTP Content-* headers and caching.
108
- def send_data(data, options = {}) #:doc:
109
+ def send_data(data, options = {}) # :doc:
109
110
  send_file_headers! options
110
111
  render options.slice(:status, :content_type).merge(body: data)
111
112
  end
@@ -132,21 +133,11 @@ 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"
142
-
143
- # Fix a problem with IE 6.0 on opening downloaded files:
144
- # If Cache-Control: no-cache is set (which Rails does by default),
145
- # IE removes the file it just downloaded from its cache immediately
146
- # after it displays the "open/save" dialog, which means that if you
147
- # hit "open" the file isn't there anymore when the application that
148
- # is called for handling the download is run, so let's workaround that
149
- response.cache_control[:public] ||= false
150
141
  end
151
142
  end
152
143
  end
@@ -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