actionpack 4.2.11.1 → 5.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 (166) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +279 -497
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -7
  5. data/lib/abstract_controller/asset_paths.rb +2 -0
  6. data/lib/abstract_controller/base.rb +45 -49
  7. data/lib/{action_controller → abstract_controller}/caching/fragments.rb +78 -15
  8. data/lib/abstract_controller/caching.rb +66 -0
  9. data/lib/abstract_controller/callbacks.rb +47 -31
  10. data/lib/abstract_controller/collector.rb +8 -11
  11. data/lib/abstract_controller/error.rb +6 -0
  12. data/lib/abstract_controller/helpers.rb +25 -25
  13. data/lib/abstract_controller/logger.rb +2 -0
  14. data/lib/abstract_controller/railties/routes_helpers.rb +4 -2
  15. data/lib/abstract_controller/rendering.rb +42 -41
  16. data/lib/abstract_controller/translation.rb +10 -7
  17. data/lib/abstract_controller/url_for.rb +2 -0
  18. data/lib/abstract_controller.rb +12 -5
  19. data/lib/action_controller/api/api_rendering.rb +16 -0
  20. data/lib/action_controller/api.rb +149 -0
  21. data/lib/action_controller/base.rb +27 -19
  22. data/lib/action_controller/caching.rb +14 -57
  23. data/lib/action_controller/form_builder.rb +50 -0
  24. data/lib/action_controller/log_subscriber.rb +10 -15
  25. data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
  26. data/lib/action_controller/metal/conditional_get.rb +118 -44
  27. data/lib/action_controller/metal/content_security_policy.rb +52 -0
  28. data/lib/action_controller/metal/cookies.rb +3 -3
  29. data/lib/action_controller/metal/data_streaming.rb +27 -46
  30. data/lib/action_controller/metal/etag_with_flash.rb +18 -0
  31. data/lib/action_controller/metal/etag_with_template_digest.rb +20 -13
  32. data/lib/action_controller/metal/exceptions.rb +8 -14
  33. data/lib/action_controller/metal/flash.rb +4 -3
  34. data/lib/action_controller/metal/force_ssl.rb +23 -21
  35. data/lib/action_controller/metal/head.rb +21 -19
  36. data/lib/action_controller/metal/helpers.rb +24 -14
  37. data/lib/action_controller/metal/http_authentication.rb +64 -57
  38. data/lib/action_controller/metal/implicit_render.rb +62 -8
  39. data/lib/action_controller/metal/instrumentation.rb +19 -21
  40. data/lib/action_controller/metal/live.rb +90 -106
  41. data/lib/action_controller/metal/mime_responds.rb +33 -46
  42. data/lib/action_controller/metal/parameter_encoding.rb +51 -0
  43. data/lib/action_controller/metal/params_wrapper.rb +61 -53
  44. data/lib/action_controller/metal/redirecting.rb +49 -28
  45. data/lib/action_controller/metal/renderers.rb +87 -44
  46. data/lib/action_controller/metal/rendering.rb +72 -50
  47. data/lib/action_controller/metal/request_forgery_protection.rb +203 -92
  48. data/lib/action_controller/metal/rescue.rb +9 -16
  49. data/lib/action_controller/metal/streaming.rb +12 -10
  50. data/lib/action_controller/metal/strong_parameters.rb +582 -165
  51. data/lib/action_controller/metal/testing.rb +2 -17
  52. data/lib/action_controller/metal/url_for.rb +19 -10
  53. data/lib/action_controller/metal.rb +98 -83
  54. data/lib/action_controller/railtie.rb +28 -10
  55. data/lib/action_controller/railties/helpers.rb +2 -0
  56. data/lib/action_controller/renderer.rb +117 -0
  57. data/lib/action_controller/template_assertions.rb +11 -0
  58. data/lib/action_controller/test_case.rb +280 -411
  59. data/lib/action_controller.rb +29 -21
  60. data/lib/action_dispatch/http/cache.rb +93 -47
  61. data/lib/action_dispatch/http/content_security_policy.rb +272 -0
  62. data/lib/action_dispatch/http/filter_parameters.rb +26 -20
  63. data/lib/action_dispatch/http/filter_redirect.rb +10 -11
  64. data/lib/action_dispatch/http/headers.rb +55 -22
  65. data/lib/action_dispatch/http/mime_negotiation.rb +56 -41
  66. data/lib/action_dispatch/http/mime_type.rb +134 -121
  67. data/lib/action_dispatch/http/mime_types.rb +20 -6
  68. data/lib/action_dispatch/http/parameter_filter.rb +25 -11
  69. data/lib/action_dispatch/http/parameters.rb +98 -39
  70. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  71. data/lib/action_dispatch/http/request.rb +200 -118
  72. data/lib/action_dispatch/http/response.rb +225 -110
  73. data/lib/action_dispatch/http/upload.rb +12 -6
  74. data/lib/action_dispatch/http/url.rb +110 -28
  75. data/lib/action_dispatch/journey/formatter.rb +55 -32
  76. data/lib/action_dispatch/journey/gtg/builder.rb +7 -5
  77. data/lib/action_dispatch/journey/gtg/simulator.rb +3 -9
  78. data/lib/action_dispatch/journey/gtg/transition_table.rb +17 -16
  79. data/lib/action_dispatch/journey/nfa/builder.rb +5 -3
  80. data/lib/action_dispatch/journey/nfa/dot.rb +13 -13
  81. data/lib/action_dispatch/journey/nfa/simulator.rb +3 -1
  82. data/lib/action_dispatch/journey/nfa/transition_table.rb +5 -48
  83. data/lib/action_dispatch/journey/nodes/node.rb +18 -6
  84. data/lib/action_dispatch/journey/parser.rb +23 -22
  85. data/lib/action_dispatch/journey/parser.y +3 -2
  86. data/lib/action_dispatch/journey/parser_extras.rb +12 -4
  87. data/lib/action_dispatch/journey/path/pattern.rb +50 -44
  88. data/lib/action_dispatch/journey/route.rb +106 -28
  89. data/lib/action_dispatch/journey/router/utils.rb +20 -11
  90. data/lib/action_dispatch/journey/router.rb +35 -23
  91. data/lib/action_dispatch/journey/routes.rb +18 -16
  92. data/lib/action_dispatch/journey/scanner.rb +18 -15
  93. data/lib/action_dispatch/journey/visitors.rb +99 -52
  94. data/lib/action_dispatch/journey.rb +7 -5
  95. data/lib/action_dispatch/middleware/callbacks.rb +1 -2
  96. data/lib/action_dispatch/middleware/cookies.rb +304 -193
  97. data/lib/action_dispatch/middleware/debug_exceptions.rb +152 -57
  98. data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
  99. data/lib/action_dispatch/middleware/exception_wrapper.rb +68 -69
  100. data/lib/action_dispatch/middleware/executor.rb +21 -0
  101. data/lib/action_dispatch/middleware/flash.rb +78 -54
  102. data/lib/action_dispatch/middleware/public_exceptions.rb +27 -25
  103. data/lib/action_dispatch/middleware/reloader.rb +5 -91
  104. data/lib/action_dispatch/middleware/remote_ip.rb +41 -31
  105. data/lib/action_dispatch/middleware/request_id.rb +17 -9
  106. data/lib/action_dispatch/middleware/session/abstract_store.rb +28 -26
  107. data/lib/action_dispatch/middleware/session/cache_store.rb +14 -9
  108. data/lib/action_dispatch/middleware/session/cookie_store.rb +62 -67
  109. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -2
  110. data/lib/action_dispatch/middleware/show_exceptions.rb +26 -22
  111. data/lib/action_dispatch/middleware/ssl.rb +114 -36
  112. data/lib/action_dispatch/middleware/stack.rb +31 -44
  113. data/lib/action_dispatch/middleware/static.rb +57 -50
  114. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +2 -14
  115. data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +0 -0
  116. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  117. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +21 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +13 -0
  119. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +1 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -1
  121. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  122. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
  123. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +64 -64
  124. data/lib/action_dispatch/railtie.rb +19 -11
  125. data/lib/action_dispatch/request/session.rb +99 -58
  126. data/lib/action_dispatch/request/utils.rb +67 -24
  127. data/lib/action_dispatch/routing/endpoint.rb +9 -2
  128. data/lib/action_dispatch/routing/inspector.rb +58 -67
  129. data/lib/action_dispatch/routing/mapper.rb +733 -447
  130. data/lib/action_dispatch/routing/polymorphic_routes.rb +161 -139
  131. data/lib/action_dispatch/routing/redirection.rb +36 -26
  132. data/lib/action_dispatch/routing/route_set.rb +321 -291
  133. data/lib/action_dispatch/routing/routes_proxy.rb +32 -5
  134. data/lib/action_dispatch/routing/url_for.rb +65 -25
  135. data/lib/action_dispatch/routing.rb +17 -18
  136. data/lib/action_dispatch/system_test_case.rb +147 -0
  137. data/lib/action_dispatch/system_testing/browser.rb +49 -0
  138. data/lib/action_dispatch/system_testing/driver.rb +59 -0
  139. data/lib/action_dispatch/system_testing/server.rb +31 -0
  140. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +96 -0
  141. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +31 -0
  142. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +26 -0
  143. data/lib/action_dispatch/testing/assertion_response.rb +47 -0
  144. data/lib/action_dispatch/testing/assertions/response.rb +45 -20
  145. data/lib/action_dispatch/testing/assertions/routing.rb +30 -26
  146. data/lib/action_dispatch/testing/assertions.rb +6 -4
  147. data/lib/action_dispatch/testing/integration.rb +347 -209
  148. data/lib/action_dispatch/testing/request_encoder.rb +55 -0
  149. data/lib/action_dispatch/testing/test_process.rb +28 -22
  150. data/lib/action_dispatch/testing/test_request.rb +27 -34
  151. data/lib/action_dispatch/testing/test_response.rb +35 -7
  152. data/lib/action_dispatch.rb +26 -19
  153. data/lib/action_pack/gem_version.rb +5 -3
  154. data/lib/action_pack/version.rb +3 -1
  155. data/lib/action_pack.rb +4 -2
  156. metadata +50 -38
  157. data/lib/action_controller/metal/hide_actions.rb +0 -40
  158. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  159. data/lib/action_controller/middleware.rb +0 -39
  160. data/lib/action_controller/model_naming.rb +0 -12
  161. data/lib/action_dispatch/journey/backwards.rb +0 -5
  162. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  163. data/lib/action_dispatch/middleware/params_parser.rb +0 -60
  164. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  165. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  166. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -1,6 +1,4 @@
1
- require 'fileutils'
2
- require 'uri'
3
- require 'set'
1
+ # frozen_string_literal: true
4
2
 
5
3
  module ActionController
6
4
  # \Caching is a cheap way of speeding up slow applications by keeping the result of
@@ -8,7 +6,7 @@ module ActionController
8
6
  #
9
7
  # You can read more about each approach by clicking the modules below.
10
8
  #
11
- # Note: To turn off all caching, set
9
+ # Note: To turn off all caching provided by Action Controller, set
12
10
  # config.action_controller.perform_caching = false
13
11
  #
14
12
  # == \Caching stores
@@ -24,66 +22,25 @@ module ActionController
24
22
  # config.action_controller.cache_store = :mem_cache_store, Memcached::Rails.new('localhost:11211')
25
23
  # config.action_controller.cache_store = MyOwnStore.new('parameter')
26
24
  module Caching
27
- extend ActiveSupport::Concern
28
25
  extend ActiveSupport::Autoload
29
-
30
- eager_autoload do
31
- autoload :Fragments
32
- end
33
-
34
- module ConfigMethods
35
- def cache_store
36
- config.cache_store
37
- end
38
-
39
- def cache_store=(store)
40
- config.cache_store = ActiveSupport::Cache.lookup_store(store)
41
- end
42
-
43
- private
44
- def cache_configured?
45
- perform_caching && cache_store
46
- end
47
- end
48
-
49
- include RackDelegation
50
- include AbstractController::Callbacks
51
-
52
- include ConfigMethods
53
- include Fragments
26
+ extend ActiveSupport::Concern
54
27
 
55
28
  included do
56
- extend ConfigMethods
57
-
58
- config_accessor :default_static_extension
59
- self.default_static_extension ||= '.html'
60
-
61
- config_accessor :perform_caching
62
- self.perform_caching = true if perform_caching.nil?
63
-
64
- class_attribute :_view_cache_dependencies
65
- self._view_cache_dependencies = []
66
- helper_method :view_cache_dependencies if respond_to?(:helper_method)
29
+ include AbstractController::Caching
67
30
  end
68
31
 
69
- module ClassMethods
70
- def view_cache_dependency(&dependency)
71
- self._view_cache_dependencies += [dependency]
72
- end
73
- end
32
+ private
74
33
 
75
- def view_cache_dependencies
76
- self.class._view_cache_dependencies.map { |dep| instance_exec(&dep) }.compact
77
- end
34
+ def instrument_payload(key)
35
+ {
36
+ controller: controller_name,
37
+ action: action_name,
38
+ key: key
39
+ }
40
+ end
78
41
 
79
- protected
80
- # Convenience accessor.
81
- def cache(key, options = {}, &block)
82
- if cache_configured?
83
- cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block)
84
- else
85
- yield
86
- end
42
+ def instrument_name
43
+ "action_controller".freeze
87
44
  end
88
45
  end
89
46
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionController
4
+ # Override the default form builder for all views rendered by this
5
+ # controller and any of its descendants. Accepts a subclass of
6
+ # +ActionView::Helpers::FormBuilder+.
7
+ #
8
+ # For example, given a form builder:
9
+ #
10
+ # class AdminFormBuilder < ActionView::Helpers::FormBuilder
11
+ # def special_field(name)
12
+ # end
13
+ # end
14
+ #
15
+ # The controller specifies a form builder as its default:
16
+ #
17
+ # class AdminAreaController < ApplicationController
18
+ # default_form_builder AdminFormBuilder
19
+ # end
20
+ #
21
+ # Then in the view any form using +form_for+ will be an instance of the
22
+ # specified form builder:
23
+ #
24
+ # <%= form_for(@instance) do |builder| %>
25
+ # <%= builder.special_field(:name) %>
26
+ # <% end %>
27
+ module FormBuilder
28
+ extend ActiveSupport::Concern
29
+
30
+ included do
31
+ class_attribute :_default_form_builder, instance_accessor: false
32
+ end
33
+
34
+ module ClassMethods
35
+ # Set the form builder to be used as the default for all forms
36
+ # in the views rendered by this controller and its subclasses.
37
+ #
38
+ # ==== Parameters
39
+ # * <tt>builder</tt> - Default form builder, an instance of +ActionView::Helpers::FormBuilder+
40
+ def default_form_builder(builder)
41
+ self._default_form_builder = builder
42
+ end
43
+ end
44
+
45
+ # Default form builder for the controller
46
+ def default_form_builder
47
+ self.class._default_form_builder
48
+ end
49
+ end
50
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionController
2
4
  class LogSubscriber < ActiveSupport::LogSubscriber
3
5
  INTERNAL_PARAMS = %w(controller action format _method only_path)
@@ -24,8 +26,10 @@ module ActionController
24
26
  exception_class_name = payload[:exception].first
25
27
  status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
26
28
  end
27
- message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms"
28
- message << " (#{additions.join(" | ")})" unless additions.blank?
29
+ message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms".dup
30
+ message << " (#{additions.join(" | ".freeze)})" unless additions.empty?
31
+ message << "\n\n" if defined?(Rails.env) && Rails.env.development?
32
+
29
33
  message
30
34
  end
31
35
  end
@@ -49,16 +53,7 @@ module ActionController
49
53
  def unpermitted_parameters(event)
50
54
  debug do
51
55
  unpermitted_keys = event.payload[:keys]
52
- "Unpermitted parameter#{'s' if unpermitted_keys.size > 1}: #{unpermitted_keys.join(", ")}"
53
- end
54
- end
55
-
56
- def deep_munge(event)
57
- debug do
58
- "Value for params[:#{event.payload[:keys].join('][:')}] was set "\
59
- "to nil, because it was one of [], [null] or [null, null, ...]. "\
60
- "Go to http://guides.rubyonrails.org/security.html#unsafe-query-generation "\
61
- "for more information."\
56
+ "Unpermitted parameter#{'s' if unpermitted_keys.size > 1}: #{unpermitted_keys.map { |e| ":#{e}" }.join(", ")}"
62
57
  end
63
58
  end
64
59
 
@@ -66,10 +61,10 @@ module ActionController
66
61
  expire_fragment expire_page write_page).each do |method|
67
62
  class_eval <<-METHOD, __FILE__, __LINE__ + 1
68
63
  def #{method}(event)
69
- return unless logger.info?
70
- key_or_path = event.payload[:key] || event.payload[:path]
64
+ return unless logger.info? && ActionController::Base.enable_fragment_cache_logging
65
+ key = ActiveSupport::Cache.expand_cache_key(event.payload[:key] || event.payload[:path])
71
66
  human_name = #{method.to_s.humanize.inspect}
72
- info("\#{human_name} \#{key_or_path} (\#{event.duration.round(1)}ms)")
67
+ info("\#{human_name} \#{key} (\#{event.duration.round(1)}ms)")
73
68
  end
74
69
  METHOD
75
70
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionController
4
+ module BasicImplicitRender # :nodoc:
5
+ def send_action(method, *args)
6
+ super.tap { default_render unless performed? }
7
+ end
8
+
9
+ def default_render(*args)
10
+ head :no_content
11
+ end
12
+ end
13
+ end
@@ -1,21 +1,21 @@
1
- require 'active_support/core_ext/hash/keys'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/keys"
2
4
 
3
5
  module ActionController
4
6
  module ConditionalGet
5
7
  extend ActiveSupport::Concern
6
8
 
7
- include RackDelegation
8
9
  include Head
9
10
 
10
11
  included do
11
- class_attribute :etaggers
12
- self.etaggers = []
12
+ class_attribute :etaggers, default: []
13
13
  end
14
14
 
15
15
  module ClassMethods
16
16
  # Allows you to consider additional controller-wide information when generating an ETag.
17
17
  # For example, if you serve pages tailored depending on who's logged in at the moment, you
18
- # may want to add the current user id to be part of the ETag to prevent authorized displaying
18
+ # may want to add the current user id to be part of the ETag to prevent unauthorized displaying
19
19
  # of cached pages.
20
20
  #
21
21
  # class InvoicesController < ApplicationController
@@ -37,10 +37,25 @@ module ActionController
37
37
  #
38
38
  # === Parameters:
39
39
  #
40
- # * <tt>:etag</tt>.
41
- # * <tt>:last_modified</tt>.
40
+ # * <tt>:etag</tt> Sets a "weak" ETag validator on the response. See the
41
+ # +:weak_etag+ option.
42
+ # * <tt>:weak_etag</tt> Sets a "weak" ETag validator on the response.
43
+ # Requests that set If-None-Match header may return a 304 Not Modified
44
+ # response if it matches the ETag exactly. A weak ETag indicates semantic
45
+ # equivalence, not byte-for-byte equality, so they're good for caching
46
+ # HTML pages in browser caches. They can't be used for responses that
47
+ # must be byte-identical, like serving Range requests within a PDF file.
48
+ # * <tt>:strong_etag</tt> Sets a "strong" ETag validator on the response.
49
+ # Requests that set If-None-Match header may return a 304 Not Modified
50
+ # response if it matches the ETag exactly. A strong ETag implies exact
51
+ # equality: the response must match byte for byte. This is necessary for
52
+ # doing Range requests within a large video or PDF file, for example, or
53
+ # for compatibility with some CDNs that don't support weak ETags.
54
+ # * <tt>:last_modified</tt> Sets a "weak" last-update validator on the
55
+ # response. Subsequent requests that set If-Modified-Since may return a
56
+ # 304 Not Modified response if last_modified <= If-Modified-Since.
42
57
  # * <tt>:public</tt> By default the Cache-Control header is private, set this to
43
- # +true+ if you want your application to be cachable by other devices (proxy caches).
58
+ # +true+ if you want your application to be cacheable by other devices (proxy caches).
44
59
  # * <tt>:template</tt> By default, the template digest for the current
45
60
  # controller/action is included in ETags. If the action renders a
46
61
  # different template, you can include its digest instead. If the action
@@ -51,21 +66,31 @@ module ActionController
51
66
  #
52
67
  # def show
53
68
  # @article = Article.find(params[:id])
54
- # fresh_when(etag: @article, last_modified: @article.created_at, public: true)
69
+ # fresh_when(etag: @article, last_modified: @article.updated_at, public: true)
55
70
  # end
56
71
  #
57
72
  # This will render the show template if the request isn't sending a matching ETag or
58
73
  # If-Modified-Since header and just a <tt>304 Not Modified</tt> response if there's a match.
59
74
  #
60
- # You can also just pass a record where +last_modified+ will be set by calling
61
- # +updated_at+ and the +etag+ by passing the object itself.
75
+ # You can also just pass a record. In this case +last_modified+ will be set
76
+ # by calling +updated_at+ and +etag+ by passing the object itself.
62
77
  #
63
78
  # def show
64
79
  # @article = Article.find(params[:id])
65
80
  # fresh_when(@article)
66
81
  # end
67
82
  #
68
- # When passing a record, you can still set whether the public header:
83
+ # You can also pass an object that responds to +maximum+, such as a
84
+ # collection of active records. In this case +last_modified+ will be set by
85
+ # calling <tt>maximum(:updated_at)</tt> on the collection (the timestamp of the
86
+ # most recently updated record) and the +etag+ by passing the object itself.
87
+ #
88
+ # def index
89
+ # @articles = Article.all
90
+ # fresh_when(@articles)
91
+ # end
92
+ #
93
+ # When passing a record or a collection, you can still set the public header:
69
94
  #
70
95
  # def show
71
96
  # @article = Article.find(params[:id])
@@ -77,18 +102,20 @@ module ActionController
77
102
  #
78
103
  # before_action { fresh_when @article, template: 'widgets/show' }
79
104
  #
80
- def fresh_when(record_or_options, additional_options = {})
81
- if record_or_options.is_a? Hash
82
- options = record_or_options
83
- options.assert_valid_keys(:etag, :last_modified, :public, :template)
84
- else
85
- record = record_or_options
86
- options = { etag: record, last_modified: record.try(:updated_at) }.merge!(additional_options)
105
+ def fresh_when(object = nil, etag: nil, weak_etag: nil, strong_etag: nil, last_modified: nil, public: false, template: nil)
106
+ weak_etag ||= etag || object unless strong_etag
107
+ last_modified ||= object.try(:updated_at) || object.try(:maximum, :updated_at)
108
+
109
+ if strong_etag
110
+ response.strong_etag = combine_etags strong_etag,
111
+ last_modified: last_modified, public: public, template: template
112
+ elsif weak_etag || template
113
+ response.weak_etag = combine_etags weak_etag,
114
+ last_modified: last_modified, public: public, template: template
87
115
  end
88
116
 
89
- response.etag = combine_etags(options) if options[:etag] || options[:template]
90
- response.last_modified = options[:last_modified] if options[:last_modified]
91
- response.cache_control[:public] = true if options[:public]
117
+ response.last_modified = last_modified if last_modified
118
+ response.cache_control[:public] = true if public
92
119
 
93
120
  head :not_modified if request.fresh?(response)
94
121
  end
@@ -100,10 +127,25 @@ module ActionController
100
127
  #
101
128
  # === Parameters:
102
129
  #
103
- # * <tt>:etag</tt>.
104
- # * <tt>:last_modified</tt>.
130
+ # * <tt>:etag</tt> Sets a "weak" ETag validator on the response. See the
131
+ # +:weak_etag+ option.
132
+ # * <tt>:weak_etag</tt> Sets a "weak" ETag validator on the response.
133
+ # Requests that set If-None-Match header may return a 304 Not Modified
134
+ # response if it matches the ETag exactly. A weak ETag indicates semantic
135
+ # equivalence, not byte-for-byte equality, so they're good for caching
136
+ # HTML pages in browser caches. They can't be used for responses that
137
+ # must be byte-identical, like serving Range requests within a PDF file.
138
+ # * <tt>:strong_etag</tt> Sets a "strong" ETag validator on the response.
139
+ # Requests that set If-None-Match header may return a 304 Not Modified
140
+ # response if it matches the ETag exactly. A strong ETag implies exact
141
+ # equality: the response must match byte for byte. This is necessary for
142
+ # doing Range requests within a large video or PDF file, for example, or
143
+ # for compatibility with some CDNs that don't support weak ETags.
144
+ # * <tt>:last_modified</tt> Sets a "weak" last-update validator on the
145
+ # response. Subsequent requests that set If-Modified-Since may return a
146
+ # 304 Not Modified response if last_modified <= If-Modified-Since.
105
147
  # * <tt>:public</tt> By default the Cache-Control header is private, set this to
106
- # +true+ if you want your application to be cachable by other devices (proxy caches).
148
+ # +true+ if you want your application to be cacheable by other devices (proxy caches).
107
149
  # * <tt>:template</tt> By default, the template digest for the current
108
150
  # controller/action is included in ETags. If the action renders a
109
151
  # different template, you can include its digest instead. If the action
@@ -115,7 +157,7 @@ module ActionController
115
157
  # def show
116
158
  # @article = Article.find(params[:id])
117
159
  #
118
- # if stale?(etag: @article, last_modified: @article.created_at)
160
+ # if stale?(etag: @article, last_modified: @article.updated_at)
119
161
  # @statistics = @article.really_expensive_call
120
162
  # respond_to do |format|
121
163
  # # all the supported formats
@@ -123,8 +165,8 @@ module ActionController
123
165
  # end
124
166
  # end
125
167
  #
126
- # You can also just pass a record where +last_modified+ will be set by calling
127
- # +updated_at+ and the +etag+ by passing the object itself.
168
+ # You can also just pass a record. In this case +last_modified+ will be set
169
+ # by calling +updated_at+ and +etag+ by passing the object itself.
128
170
  #
129
171
  # def show
130
172
  # @article = Article.find(params[:id])
@@ -137,7 +179,23 @@ module ActionController
137
179
  # end
138
180
  # end
139
181
  #
140
- # When passing a record, you can still set whether the public header:
182
+ # You can also pass an object that responds to +maximum+, such as a
183
+ # 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
+ # most recently updated record) and the +etag+ by passing the object itself.
186
+ #
187
+ # def index
188
+ # @articles = Article.all
189
+ #
190
+ # if stale?(@articles)
191
+ # @statistics = @articles.really_expensive_call
192
+ # respond_to do |format|
193
+ # # all the supported formats
194
+ # end
195
+ # end
196
+ # end
197
+ #
198
+ # When passing a record or a collection, you can still set the public header:
141
199
  #
142
200
  # def show
143
201
  # @article = Article.find(params[:id])
@@ -157,12 +215,12 @@ module ActionController
157
215
  # super if stale? @article, template: 'widgets/show'
158
216
  # end
159
217
  #
160
- def stale?(record_or_options, additional_options = {})
161
- fresh_when(record_or_options, additional_options)
218
+ def stale?(object = nil, **freshness_kwargs)
219
+ fresh_when(object, **freshness_kwargs)
162
220
  !request.fresh?(response)
163
221
  end
164
222
 
165
- # Sets a HTTP 1.1 Cache-Control header. Defaults to issuing a +private+
223
+ # Sets an HTTP 1.1 Cache-Control header. Defaults to issuing a +private+
166
224
  # instruction, so that intermediate caches must not cache the response.
167
225
  #
168
226
  # expires_in 20.minutes
@@ -170,31 +228,47 @@ module ActionController
170
228
  # expires_in 3.hours, public: true, must_revalidate: true
171
229
  #
172
230
  # This method will overwrite an existing Cache-Control header.
173
- # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
231
+ # See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
174
232
  #
175
- # The method will also ensure a HTTP Date header for client compatibility.
233
+ # The method will also ensure an HTTP Date header for client compatibility.
176
234
  def expires_in(seconds, options = {})
177
235
  response.cache_control.merge!(
178
- :max_age => seconds,
179
- :public => options.delete(:public),
180
- :must_revalidate => options.delete(:must_revalidate)
236
+ max_age: seconds,
237
+ public: options.delete(:public),
238
+ must_revalidate: options.delete(:must_revalidate)
181
239
  )
182
240
  options.delete(:private)
183
241
 
184
- response.cache_control[:extras] = options.map {|k,v| "#{k}=#{v}"}
242
+ response.cache_control[:extras] = options.map { |k, v| "#{k}=#{v}" }
185
243
  response.date = Time.now unless response.date?
186
244
  end
187
245
 
188
- # Sets a HTTP 1.1 Cache-Control header of <tt>no-cache</tt> so no caching should
189
- # occur by the browser or intermediate caches (like caching proxy servers).
246
+ # Sets an HTTP 1.1 Cache-Control header of <tt>no-cache</tt>. This means the
247
+ # resource will be marked as stale, so clients must always revalidate.
248
+ # Intermediate/browser caches may still store the asset.
190
249
  def expires_now
191
- response.cache_control.replace(:no_cache => true)
250
+ response.cache_control.replace(no_cache: true)
251
+ end
252
+
253
+ # Cache or yield the block. The cache is supposed to never expire.
254
+ #
255
+ # You can use this method when you have an HTTP response that never changes,
256
+ # and the browser and proxies should cache it indefinitely.
257
+ #
258
+ # * +public+: By default, HTTP responses are private, cached only on the
259
+ # user's web browser. To allow proxies to cache the response, set +true+ to
260
+ # indicate that they can serve the cached response to all users.
261
+ def http_cache_forever(public: false)
262
+ expires_in 100.years, public: public
263
+
264
+ yield if stale?(etag: request.fullpath,
265
+ last_modified: Time.new(2011, 1, 1).utc,
266
+ public: public)
192
267
  end
193
268
 
194
269
  private
195
- def combine_etags(options)
196
- etags = etaggers.map { |etagger| instance_exec(options, &etagger) }.compact
197
- etags.unshift options[:etag]
270
+ def combine_etags(validator, options)
271
+ [validator, *etaggers.map { |etagger| instance_exec(options, &etagger) }].compact
198
272
  end
199
273
  end
200
274
  end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionController #:nodoc:
4
+ module ContentSecurityPolicy
5
+ # TODO: Documentation
6
+ extend ActiveSupport::Concern
7
+
8
+ include AbstractController::Helpers
9
+ include AbstractController::Callbacks
10
+
11
+ included do
12
+ helper_method :content_security_policy?
13
+ helper_method :content_security_policy_nonce
14
+ end
15
+
16
+ module ClassMethods
17
+ def content_security_policy(enabled = true, **options, &block)
18
+ before_action(options) do
19
+ if block_given?
20
+ policy = current_content_security_policy
21
+ yield policy
22
+ request.content_security_policy = policy
23
+ end
24
+
25
+ unless enabled
26
+ request.content_security_policy = nil
27
+ end
28
+ end
29
+ end
30
+
31
+ def content_security_policy_report_only(report_only = true, **options)
32
+ before_action(options) do
33
+ request.content_security_policy_report_only = report_only
34
+ end
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def content_security_policy?
41
+ request.content_security_policy
42
+ end
43
+
44
+ def content_security_policy_nonce
45
+ request.content_security_policy_nonce
46
+ end
47
+
48
+ def current_content_security_policy
49
+ request.content_security_policy.try(:clone) || ActionDispatch::ContentSecurityPolicy.new
50
+ end
51
+ end
52
+ end
@@ -1,11 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionController #:nodoc:
2
4
  module Cookies
3
5
  extend ActiveSupport::Concern
4
6
 
5
- include RackDelegation
6
-
7
7
  included do
8
- helper_method :cookies
8
+ helper_method :cookies if defined?(helper_method)
9
9
  end
10
10
 
11
11
  private