actionpack 4.2.10 → 7.2.0.rc1

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 (202) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +86 -600
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +13 -14
  5. data/lib/abstract_controller/asset_paths.rb +5 -1
  6. data/lib/abstract_controller/base.rb +166 -136
  7. data/lib/abstract_controller/caching/fragments.rb +149 -0
  8. data/lib/abstract_controller/caching.rb +68 -0
  9. data/lib/abstract_controller/callbacks.rb +126 -57
  10. data/lib/abstract_controller/collector.rb +13 -15
  11. data/lib/abstract_controller/deprecator.rb +9 -0
  12. data/lib/abstract_controller/error.rb +8 -0
  13. data/lib/abstract_controller/helpers.rb +181 -132
  14. data/lib/abstract_controller/logger.rb +5 -1
  15. data/lib/abstract_controller/railties/routes_helpers.rb +10 -3
  16. data/lib/abstract_controller/rendering.rb +56 -56
  17. data/lib/abstract_controller/translation.rb +29 -15
  18. data/lib/abstract_controller/url_for.rb +15 -11
  19. data/lib/abstract_controller.rb +21 -5
  20. data/lib/action_controller/api/api_rendering.rb +18 -0
  21. data/lib/action_controller/api.rb +154 -0
  22. data/lib/action_controller/base.rb +219 -155
  23. data/lib/action_controller/caching.rb +28 -68
  24. data/lib/action_controller/deprecator.rb +9 -0
  25. data/lib/action_controller/form_builder.rb +55 -0
  26. data/lib/action_controller/log_subscriber.rb +35 -22
  27. data/lib/action_controller/metal/allow_browser.rb +119 -0
  28. data/lib/action_controller/metal/basic_implicit_render.rb +17 -0
  29. data/lib/action_controller/metal/conditional_get.rb +259 -122
  30. data/lib/action_controller/metal/content_security_policy.rb +86 -0
  31. data/lib/action_controller/metal/cookies.rb +9 -5
  32. data/lib/action_controller/metal/data_streaming.rb +87 -104
  33. data/lib/action_controller/metal/default_headers.rb +21 -0
  34. data/lib/action_controller/metal/etag_with_flash.rb +22 -0
  35. data/lib/action_controller/metal/etag_with_template_digest.rb +35 -26
  36. data/lib/action_controller/metal/exceptions.rb +71 -24
  37. data/lib/action_controller/metal/flash.rb +26 -19
  38. data/lib/action_controller/metal/head.rb +45 -36
  39. data/lib/action_controller/metal/helpers.rb +80 -64
  40. data/lib/action_controller/metal/http_authentication.rb +297 -244
  41. data/lib/action_controller/metal/implicit_render.rb +57 -9
  42. data/lib/action_controller/metal/instrumentation.rb +76 -64
  43. data/lib/action_controller/metal/live.rb +238 -176
  44. data/lib/action_controller/metal/logging.rb +22 -0
  45. data/lib/action_controller/metal/mime_responds.rb +177 -166
  46. data/lib/action_controller/metal/parameter_encoding.rb +84 -0
  47. data/lib/action_controller/metal/params_wrapper.rb +145 -118
  48. data/lib/action_controller/metal/permissions_policy.rb +38 -0
  49. data/lib/action_controller/metal/rate_limiting.rb +62 -0
  50. data/lib/action_controller/metal/redirecting.rb +203 -64
  51. data/lib/action_controller/metal/renderers.rb +108 -65
  52. data/lib/action_controller/metal/rendering.rb +216 -56
  53. data/lib/action_controller/metal/request_forgery_protection.rb +496 -163
  54. data/lib/action_controller/metal/rescue.rb +19 -21
  55. data/lib/action_controller/metal/streaming.rb +179 -138
  56. data/lib/action_controller/metal/strong_parameters.rb +1058 -382
  57. data/lib/action_controller/metal/testing.rb +11 -17
  58. data/lib/action_controller/metal/url_for.rb +37 -21
  59. data/lib/action_controller/metal.rb +236 -138
  60. data/lib/action_controller/railtie.rb +89 -11
  61. data/lib/action_controller/railties/helpers.rb +5 -1
  62. data/lib/action_controller/renderer.rb +161 -0
  63. data/lib/action_controller/template_assertions.rb +13 -0
  64. data/lib/action_controller/test_case.rb +425 -497
  65. data/lib/action_controller.rb +44 -22
  66. data/lib/action_dispatch/constants.rb +34 -0
  67. data/lib/action_dispatch/deprecator.rb +9 -0
  68. data/lib/action_dispatch/http/cache.rb +119 -63
  69. data/lib/action_dispatch/http/content_disposition.rb +47 -0
  70. data/lib/action_dispatch/http/content_security_policy.rb +364 -0
  71. data/lib/action_dispatch/http/filter_parameters.rb +36 -34
  72. data/lib/action_dispatch/http/filter_redirect.rb +24 -12
  73. data/lib/action_dispatch/http/headers.rb +66 -31
  74. data/lib/action_dispatch/http/mime_negotiation.rb +106 -75
  75. data/lib/action_dispatch/http/mime_type.rb +196 -136
  76. data/lib/action_dispatch/http/mime_types.rb +25 -7
  77. data/lib/action_dispatch/http/parameters.rb +97 -45
  78. data/lib/action_dispatch/http/permissions_policy.rb +187 -0
  79. data/lib/action_dispatch/http/rack_cache.rb +6 -0
  80. data/lib/action_dispatch/http/request.rb +299 -170
  81. data/lib/action_dispatch/http/response.rb +311 -160
  82. data/lib/action_dispatch/http/upload.rb +52 -23
  83. data/lib/action_dispatch/http/url.rb +201 -125
  84. data/lib/action_dispatch/journey/formatter.rb +110 -50
  85. data/lib/action_dispatch/journey/gtg/builder.rb +37 -50
  86. data/lib/action_dispatch/journey/gtg/simulator.rb +20 -17
  87. data/lib/action_dispatch/journey/gtg/transition_table.rb +96 -36
  88. data/lib/action_dispatch/journey/nfa/dot.rb +5 -14
  89. data/lib/action_dispatch/journey/nodes/node.rb +100 -20
  90. data/lib/action_dispatch/journey/parser.rb +19 -17
  91. data/lib/action_dispatch/journey/parser.y +4 -3
  92. data/lib/action_dispatch/journey/parser_extras.rb +14 -4
  93. data/lib/action_dispatch/journey/path/pattern.rb +79 -63
  94. data/lib/action_dispatch/journey/route.rb +108 -44
  95. data/lib/action_dispatch/journey/router/utils.rb +41 -29
  96. data/lib/action_dispatch/journey/router.rb +64 -57
  97. data/lib/action_dispatch/journey/routes.rb +23 -21
  98. data/lib/action_dispatch/journey/scanner.rb +28 -17
  99. data/lib/action_dispatch/journey/visitors.rb +100 -54
  100. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  101. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  102. data/lib/action_dispatch/journey.rb +7 -5
  103. data/lib/action_dispatch/log_subscriber.rb +25 -0
  104. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  105. data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
  106. data/lib/action_dispatch/middleware/callbacks.rb +7 -6
  107. data/lib/action_dispatch/middleware/cookies.rb +471 -328
  108. data/lib/action_dispatch/middleware/debug_exceptions.rb +149 -66
  109. data/lib/action_dispatch/middleware/debug_locks.rb +129 -0
  110. data/lib/action_dispatch/middleware/debug_view.rb +73 -0
  111. data/lib/action_dispatch/middleware/exception_wrapper.rb +275 -73
  112. data/lib/action_dispatch/middleware/executor.rb +32 -0
  113. data/lib/action_dispatch/middleware/flash.rb +143 -101
  114. data/lib/action_dispatch/middleware/host_authorization.rb +171 -0
  115. data/lib/action_dispatch/middleware/public_exceptions.rb +36 -27
  116. data/lib/action_dispatch/middleware/reloader.rb +10 -92
  117. data/lib/action_dispatch/middleware/remote_ip.rb +133 -107
  118. data/lib/action_dispatch/middleware/request_id.rb +29 -15
  119. data/lib/action_dispatch/middleware/server_timing.rb +78 -0
  120. data/lib/action_dispatch/middleware/session/abstract_store.rb +49 -27
  121. data/lib/action_dispatch/middleware/session/cache_store.rb +33 -16
  122. data/lib/action_dispatch/middleware/session/cookie_store.rb +86 -80
  123. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +15 -3
  124. data/lib/action_dispatch/middleware/show_exceptions.rb +66 -36
  125. data/lib/action_dispatch/middleware/ssl.rb +134 -36
  126. data/lib/action_dispatch/middleware/stack.rb +109 -44
  127. data/lib/action_dispatch/middleware/static.rb +159 -90
  128. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +7 -24
  132. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  133. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +36 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +46 -36
  136. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +12 -0
  137. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +9 -0
  138. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +26 -7
  139. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +3 -3
  140. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
  141. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +16 -0
  142. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +139 -15
  143. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +23 -0
  144. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  145. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +6 -6
  146. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +7 -7
  147. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +9 -9
  148. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  149. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
  150. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  151. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +7 -4
  152. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +125 -93
  153. data/lib/action_dispatch/railtie.rb +44 -16
  154. data/lib/action_dispatch/request/session.rb +159 -69
  155. data/lib/action_dispatch/request/utils.rb +97 -23
  156. data/lib/action_dispatch/routing/endpoint.rb +11 -2
  157. data/lib/action_dispatch/routing/inspector.rb +195 -106
  158. data/lib/action_dispatch/routing/mapper.rb +1338 -955
  159. data/lib/action_dispatch/routing/polymorphic_routes.rb +234 -201
  160. data/lib/action_dispatch/routing/redirection.rb +78 -51
  161. data/lib/action_dispatch/routing/route_set.rb +460 -374
  162. data/lib/action_dispatch/routing/routes_proxy.rb +36 -12
  163. data/lib/action_dispatch/routing/url_for.rb +172 -124
  164. data/lib/action_dispatch/routing.rb +159 -158
  165. data/lib/action_dispatch/system_test_case.rb +206 -0
  166. data/lib/action_dispatch/system_testing/browser.rb +84 -0
  167. data/lib/action_dispatch/system_testing/driver.rb +85 -0
  168. data/lib/action_dispatch/system_testing/server.rb +33 -0
  169. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +164 -0
  170. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +23 -0
  171. data/lib/action_dispatch/testing/assertion_response.rb +48 -0
  172. data/lib/action_dispatch/testing/assertions/response.rb +71 -39
  173. data/lib/action_dispatch/testing/assertions/routing.rb +228 -103
  174. data/lib/action_dispatch/testing/assertions.rb +9 -6
  175. data/lib/action_dispatch/testing/integration.rb +486 -306
  176. data/lib/action_dispatch/testing/request_encoder.rb +60 -0
  177. data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
  178. data/lib/action_dispatch/testing/test_process.rb +35 -22
  179. data/lib/action_dispatch/testing/test_request.rb +29 -34
  180. data/lib/action_dispatch/testing/test_response.rb +48 -15
  181. data/lib/action_dispatch.rb +82 -40
  182. data/lib/action_pack/gem_version.rb +8 -4
  183. data/lib/action_pack/version.rb +6 -2
  184. data/lib/action_pack.rb +21 -18
  185. metadata +146 -56
  186. data/lib/action_controller/caching/fragments.rb +0 -103
  187. data/lib/action_controller/metal/force_ssl.rb +0 -97
  188. data/lib/action_controller/metal/hide_actions.rb +0 -40
  189. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  190. data/lib/action_controller/middleware.rb +0 -39
  191. data/lib/action_controller/model_naming.rb +0 -12
  192. data/lib/action_dispatch/http/parameter_filter.rb +0 -72
  193. data/lib/action_dispatch/journey/backwards.rb +0 -5
  194. data/lib/action_dispatch/journey/nfa/builder.rb +0 -76
  195. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
  196. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -163
  197. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  198. data/lib/action_dispatch/middleware/params_parser.rb +0 -60
  199. data/lib/action_dispatch/middleware/templates/rescues/_source.erb +0 -27
  200. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  201. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  202. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -1,200 +1,337 @@
1
- require 'active_support/core_ext/hash/keys'
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "active_support/core_ext/object/try"
6
+ require "active_support/core_ext/integer/time"
2
7
 
3
8
  module ActionController
4
9
  module ConditionalGet
5
10
  extend ActiveSupport::Concern
6
11
 
7
- include RackDelegation
8
12
  include Head
9
13
 
10
14
  included do
11
- class_attribute :etaggers
12
- self.etaggers = []
15
+ class_attribute :etaggers, default: []
13
16
  end
14
17
 
15
18
  module ClassMethods
16
- # Allows you to consider additional controller-wide information when generating an ETag.
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
19
- # of cached pages.
19
+ # Allows you to consider additional controller-wide information when generating
20
+ # an ETag. For example, if you serve pages tailored depending on who's logged in
21
+ # at the moment, you may want to add the current user id to be part of the ETag
22
+ # to prevent unauthorized displaying of cached pages.
20
23
  #
21
- # class InvoicesController < ApplicationController
22
- # etag { current_user.try :id }
24
+ # class InvoicesController < ApplicationController
25
+ # etag { current_user&.id }
23
26
  #
24
- # def show
25
- # # Etag will differ even for the same invoice when it's viewed by a different current_user
26
- # @invoice = Invoice.find(params[:id])
27
- # fresh_when(@invoice)
27
+ # def show
28
+ # # Etag will differ even for the same invoice when it's viewed by a different current_user
29
+ # @invoice = Invoice.find(params[:id])
30
+ # fresh_when etag: @invoice
31
+ # end
28
32
  # end
29
- # end
30
33
  def etag(&etagger)
31
34
  self.etaggers += [etagger]
32
35
  end
33
36
  end
34
37
 
35
- # Sets the +etag+, +last_modified+, or both on the response and renders a
36
- # <tt>304 Not Modified</tt> response if the request is already fresh.
38
+ # Sets the `etag`, `last_modified`, or both on the response, and renders a `304
39
+ # Not Modified` response if the request is already fresh.
40
+ #
41
+ # #### Options
42
+ #
43
+ # `:etag`
44
+ # : Sets a "weak" ETag validator on the response. See the `:weak_etag` option.
37
45
  #
38
- # === Parameters:
46
+ # `:weak_etag`
47
+ # : Sets a "weak" ETag validator on the response. Requests that specify an
48
+ # `If-None-Match` header may receive a `304 Not Modified` response if the
49
+ # ETag matches exactly.
39
50
  #
40
- # * <tt>:etag</tt>.
41
- # * <tt>:last_modified</tt>.
42
- # * <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).
44
- # * <tt>:template</tt> By default, the template digest for the current
45
- # controller/action is included in ETags. If the action renders a
46
- # different template, you can include its digest instead. If the action
47
- # doesn't render a template at all, you can pass <tt>template: false</tt>
48
- # to skip any attempt to check for a template digest.
51
+ # : A weak ETag indicates semantic equivalence, not byte-for-byte equality, so
52
+ # they're good for caching HTML pages in browser caches. They can't be used
53
+ # for responses that must be byte-identical, like serving `Range` requests
54
+ # within a PDF file.
49
55
  #
50
- # === Example:
56
+ # `:strong_etag`
57
+ # : Sets a "strong" ETag validator on the response. Requests that specify an
58
+ # `If-None-Match` header may receive a `304 Not Modified` response if the
59
+ # ETag matches exactly.
51
60
  #
52
- # def show
53
- # @article = Article.find(params[:id])
54
- # fresh_when(etag: @article, last_modified: @article.created_at, public: true)
55
- # end
61
+ # : A strong ETag implies exact equality -- the response must match byte for
62
+ # byte. This is necessary for serving `Range` requests within a large video
63
+ # or PDF file, for example, or for compatibility with some CDNs that don't
64
+ # support weak ETags.
56
65
  #
57
- # This will render the show template if the request isn't sending a matching ETag or
58
- # If-Modified-Since header and just a <tt>304 Not Modified</tt> response if there's a match.
66
+ # `:last_modified`
67
+ # : Sets a "weak" last-update validator on the response. Subsequent requests
68
+ # that specify an `If-Modified-Since` header may receive a `304 Not
69
+ # Modified` response if `last_modified` <= `If-Modified-Since`.
59
70
  #
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.
71
+ # `:public`
72
+ # : By default the `Cache-Control` header is private. Set this option to
73
+ # `true` if you want your application to be cacheable by other devices, such
74
+ # as proxy caches.
62
75
  #
63
- # def show
64
- # @article = Article.find(params[:id])
65
- # fresh_when(@article)
66
- # end
76
+ # `:cache_control`
77
+ # : When given, will overwrite an existing `Cache-Control` header. For a list
78
+ # of `Cache-Control` directives, see the [article on
79
+ # MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Contr
80
+ # ol).
67
81
  #
68
- # When passing a record, you can still set whether the public header:
82
+ # `:template`
83
+ # : By default, the template digest for the current controller/action is
84
+ # included in ETags. If the action renders a different template, you can
85
+ # include its digest instead. If the action doesn't render a template at
86
+ # all, you can pass `template: false` to skip any attempt to check for a
87
+ # template digest.
69
88
  #
70
- # def show
71
- # @article = Article.find(params[:id])
72
- # fresh_when(@article, public: true)
73
- # end
74
89
  #
75
- # When rendering a different template than the default controller/action
76
- # style, you can indicate which digest to include in the ETag:
90
+ # #### Examples
91
+ #
92
+ # def show
93
+ # @article = Article.find(params[:id])
94
+ # fresh_when(etag: @article, last_modified: @article.updated_at, public: true)
95
+ # end
96
+ #
97
+ # This will send a `304 Not Modified` response if the request specifies a
98
+ # matching ETag and `If-Modified-Since` header. Otherwise, it will render the
99
+ # `show` template.
77
100
  #
78
- # before_action { fresh_when @article, template: 'widgets/show' }
101
+ # You can also just pass a record:
102
+ #
103
+ # def show
104
+ # @article = Article.find(params[:id])
105
+ # fresh_when(@article)
106
+ # end
107
+ #
108
+ # `etag` will be set to the record, and `last_modified` will be set to the
109
+ # record's `updated_at`.
110
+ #
111
+ # You can also pass an object that responds to `maximum`, such as a collection
112
+ # of records:
113
+ #
114
+ # def index
115
+ # @articles = Article.all
116
+ # fresh_when(@articles)
117
+ # end
79
118
  #
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)
119
+ # In this case, `etag` will be set to the collection, and `last_modified` will
120
+ # be set to `maximum(:updated_at)` (the timestamp of the most recently updated
121
+ # record).
122
+ #
123
+ # When passing a record or a collection, you can still specify other options,
124
+ # such as `:public` and `:cache_control`:
125
+ #
126
+ # def show
127
+ # @article = Article.find(params[:id])
128
+ # fresh_when(@article, public: true, cache_control: { no_cache: true })
129
+ # end
130
+ #
131
+ # The above will set `Cache-Control: public, no-cache` in the response.
132
+ #
133
+ # When rendering a different template than the controller/action's default
134
+ # template, you can indicate which digest to include in the ETag:
135
+ #
136
+ # before_action { fresh_when @article, template: "widgets/show" }
137
+ #
138
+ def fresh_when(object = nil, etag: nil, weak_etag: nil, strong_etag: nil, last_modified: nil, public: false, cache_control: {}, template: nil)
139
+ response.cache_control.delete(:no_store)
140
+ weak_etag ||= etag || object unless strong_etag
141
+ last_modified ||= object.try(:updated_at) || object.try(:maximum, :updated_at)
142
+
143
+ if strong_etag
144
+ response.strong_etag = combine_etags strong_etag,
145
+ last_modified: last_modified, public: public, template: template
146
+ elsif weak_etag || template
147
+ response.weak_etag = combine_etags weak_etag,
148
+ last_modified: last_modified, public: public, template: template
87
149
  end
88
150
 
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]
151
+ response.last_modified = last_modified if last_modified
152
+ response.cache_control[:public] = true if public
153
+ response.cache_control.merge!(cache_control)
92
154
 
93
155
  head :not_modified if request.fresh?(response)
94
156
  end
95
157
 
96
- # Sets the +etag+ and/or +last_modified+ on the response and checks it against
97
- # the client request. If the request doesn't match the options provided, the
98
- # request is considered stale and should be generated from scratch. Otherwise,
99
- # it's fresh and we don't need to generate anything and a reply of <tt>304 Not Modified</tt> is sent.
158
+ # Sets the `etag` and/or `last_modified` on the response and checks them against
159
+ # the request. If the request doesn't match the provided options, it is
160
+ # considered stale, and the response should be rendered from scratch. Otherwise,
161
+ # it is fresh, and a `304 Not Modified` is sent.
162
+ #
163
+ # #### Options
100
164
  #
101
- # === Parameters:
165
+ # See #fresh_when for supported options.
102
166
  #
103
- # * <tt>:etag</tt>.
104
- # * <tt>:last_modified</tt>.
105
- # * <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).
107
- # * <tt>:template</tt> By default, the template digest for the current
108
- # controller/action is included in ETags. If the action renders a
109
- # different template, you can include its digest instead. If the action
110
- # doesn't render a template at all, you can pass <tt>template: false</tt>
111
- # to skip any attempt to check for a template digest.
167
+ # #### Examples
168
+ #
169
+ # def show
170
+ # @article = Article.find(params[:id])
171
+ #
172
+ # if stale?(etag: @article, last_modified: @article.updated_at)
173
+ # @statistics = @article.really_expensive_call
174
+ # respond_to do |format|
175
+ # # all the supported formats
176
+ # end
177
+ # end
178
+ # end
112
179
  #
113
- # === Example:
180
+ # You can also just pass a record:
114
181
  #
115
- # def show
116
- # @article = Article.find(params[:id])
182
+ # def show
183
+ # @article = Article.find(params[:id])
117
184
  #
118
- # if stale?(etag: @article, last_modified: @article.created_at)
119
- # @statistics = @article.really_expensive_call
120
- # respond_to do |format|
121
- # # all the supported formats
185
+ # if stale?(@article)
186
+ # @statistics = @article.really_expensive_call
187
+ # respond_to do |format|
188
+ # # all the supported formats
189
+ # end
122
190
  # end
123
191
  # end
124
- # end
125
192
  #
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.
193
+ # `etag` will be set to the record, and `last_modified` will be set to the
194
+ # record's `updated_at`.
195
+ #
196
+ # You can also pass an object that responds to `maximum`, such as a collection
197
+ # of records:
128
198
  #
129
- # def show
130
- # @article = Article.find(params[:id])
199
+ # def index
200
+ # @articles = Article.all
131
201
  #
132
- # if stale?(@article)
133
- # @statistics = @article.really_expensive_call
134
- # respond_to do |format|
135
- # # all the supported formats
202
+ # if stale?(@articles)
203
+ # @statistics = @articles.really_expensive_call
204
+ # respond_to do |format|
205
+ # # all the supported formats
206
+ # end
136
207
  # end
137
208
  # end
138
- # end
139
209
  #
140
- # When passing a record, you can still set whether the public header:
210
+ # In this case, `etag` will be set to the collection, and `last_modified` will
211
+ # be set to `maximum(:updated_at)` (the timestamp of the most recently updated
212
+ # record).
141
213
  #
142
- # def show
143
- # @article = Article.find(params[:id])
214
+ # When passing a record or a collection, you can still specify other options,
215
+ # such as `:public` and `:cache_control`:
144
216
  #
145
- # if stale?(@article, public: true)
146
- # @statistics = @article.really_expensive_call
147
- # respond_to do |format|
148
- # # all the supported formats
217
+ # def show
218
+ # @article = Article.find(params[:id])
219
+ #
220
+ # if stale?(@article, public: true, cache_control: { no_cache: true })
221
+ # @statistics = @articles.really_expensive_call
222
+ # respond_to do |format|
223
+ # # all the supported formats
224
+ # end
149
225
  # end
150
226
  # end
151
- # end
152
227
  #
153
- # When rendering a different template than the default controller/action
154
- # style, you can indicate which digest to include in the ETag:
228
+ # The above will set `Cache-Control: public, no-cache` in the response.
155
229
  #
156
- # def show
157
- # super if stale? @article, template: 'widgets/show'
158
- # end
230
+ # When rendering a different template than the controller/action's default
231
+ # template, you can indicate which digest to include in the ETag:
159
232
  #
160
- def stale?(record_or_options, additional_options = {})
161
- fresh_when(record_or_options, additional_options)
233
+ # def show
234
+ # super if stale?(@article, template: "widgets/show")
235
+ # end
236
+ #
237
+ def stale?(object = nil, **freshness_kwargs)
238
+ fresh_when(object, **freshness_kwargs)
162
239
  !request.fresh?(response)
163
240
  end
164
241
 
165
- # Sets a HTTP 1.1 Cache-Control header. Defaults to issuing a +private+
166
- # instruction, so that intermediate caches must not cache the response.
242
+ # Sets the `Cache-Control` header, overwriting existing directives. This method
243
+ # will also ensure an HTTP `Date` header for client compatibility.
244
+ #
245
+ # Defaults to issuing the `private` directive, so that intermediate caches must
246
+ # not cache the response.
247
+ #
248
+ # #### Options
249
+ #
250
+ # `:public`
251
+ # : If true, replaces the default `private` directive with the `public`
252
+ # directive.
253
+ #
254
+ # `:must_revalidate`
255
+ # : If true, adds the `must-revalidate` directive.
256
+ #
257
+ # `:stale_while_revalidate`
258
+ # : Sets the value of the `stale-while-revalidate` directive.
259
+ #
260
+ # `:stale_if_error`
261
+ # : Sets the value of the `stale-if-error` directive.
262
+ #
263
+ #
264
+ # Any additional key-value pairs are concatenated as directives. For a list of
265
+ # supported `Cache-Control` directives, see the [article on
266
+ # MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control).
167
267
  #
168
- # expires_in 20.minutes
169
- # expires_in 3.hours, public: true
170
- # expires_in 3.hours, public: true, must_revalidate: true
268
+ # #### Examples
171
269
  #
172
- # This method will overwrite an existing Cache-Control header.
173
- # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
270
+ # expires_in 10.minutes
271
+ # # => Cache-Control: max-age=600, private
272
+ #
273
+ # expires_in 10.minutes, public: true
274
+ # # => Cache-Control: max-age=600, public
275
+ #
276
+ # expires_in 10.minutes, public: true, must_revalidate: true
277
+ # # => Cache-Control: max-age=600, public, must-revalidate
278
+ #
279
+ # expires_in 1.hour, stale_while_revalidate: 60.seconds
280
+ # # => Cache-Control: max-age=3600, private, stale-while-revalidate=60
281
+ #
282
+ # expires_in 1.hour, stale_if_error: 5.minutes
283
+ # # => Cache-Control: max-age=3600, private, stale-if-error=300
284
+ #
285
+ # expires_in 1.hour, public: true, "s-maxage": 3.hours, "no-transform": true
286
+ # # => Cache-Control: max-age=3600, public, s-maxage=10800, no-transform=true
174
287
  #
175
- # The method will also ensure a HTTP Date header for client compatibility.
176
288
  def expires_in(seconds, options = {})
289
+ response.cache_control.delete(:no_store)
177
290
  response.cache_control.merge!(
178
- :max_age => seconds,
179
- :public => options.delete(:public),
180
- :must_revalidate => options.delete(:must_revalidate)
291
+ max_age: seconds,
292
+ public: options.delete(:public),
293
+ must_revalidate: options.delete(:must_revalidate),
294
+ stale_while_revalidate: options.delete(:stale_while_revalidate),
295
+ stale_if_error: options.delete(:stale_if_error),
181
296
  )
182
297
  options.delete(:private)
183
298
 
184
- response.cache_control[:extras] = options.map {|k,v| "#{k}=#{v}"}
299
+ response.cache_control[:extras] = options.map { |k, v| "#{k}=#{v}" }
185
300
  response.date = Time.now unless response.date?
186
301
  end
187
302
 
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).
303
+ # Sets an HTTP 1.1 `Cache-Control` header of `no-cache`. This means the resource
304
+ # will be marked as stale, so clients must always revalidate.
305
+ # Intermediate/browser caches may still store the asset.
190
306
  def expires_now
191
- response.cache_control.replace(:no_cache => true)
307
+ response.cache_control.replace(no_cache: true)
308
+ end
309
+
310
+ # Cache or yield the block. The cache is supposed to never expire.
311
+ #
312
+ # You can use this method when you have an HTTP response that never changes, and
313
+ # the browser and proxies should cache it indefinitely.
314
+ #
315
+ # * `public`: By default, HTTP responses are private, cached only on the
316
+ # user's web browser. To allow proxies to cache the response, set `true` to
317
+ # indicate that they can serve the cached response to all users.
318
+ def http_cache_forever(public: false)
319
+ expires_in 100.years, public: public
320
+
321
+ yield if stale?(etag: request.fullpath,
322
+ last_modified: Time.new(2011, 1, 1).utc,
323
+ public: public)
324
+ end
325
+
326
+ # Sets an HTTP 1.1 `Cache-Control` header of `no-store`. This means the resource
327
+ # may not be stored in any cache.
328
+ def no_store
329
+ response.cache_control.replace(no_store: true)
192
330
  end
193
331
 
194
332
  private
195
- def combine_etags(options)
196
- etags = etaggers.map { |etagger| instance_exec(options, &etagger) }.compact
197
- etags.unshift options[:etag]
333
+ def combine_etags(validator, options)
334
+ [validator, *etaggers.map { |etagger| instance_exec(options, &etagger) }].compact
198
335
  end
199
336
  end
200
337
  end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ module ActionController # :nodoc:
6
+ module ContentSecurityPolicy
7
+ extend ActiveSupport::Concern
8
+
9
+ include AbstractController::Helpers
10
+ include AbstractController::Callbacks
11
+
12
+ included do
13
+ helper_method :content_security_policy?
14
+ helper_method :content_security_policy_nonce
15
+ end
16
+
17
+ module ClassMethods
18
+ # Overrides parts of the globally configured `Content-Security-Policy` header:
19
+ #
20
+ # class PostsController < ApplicationController
21
+ # content_security_policy do |policy|
22
+ # policy.base_uri "https://www.example.com"
23
+ # end
24
+ # end
25
+ #
26
+ # Options can be passed similar to `before_action`. For example, pass `only:
27
+ # :index` to override the header on the index action only:
28
+ #
29
+ # class PostsController < ApplicationController
30
+ # content_security_policy(only: :index) do |policy|
31
+ # policy.default_src :self, :https
32
+ # end
33
+ # end
34
+ #
35
+ # Pass `false` to remove the `Content-Security-Policy` header:
36
+ #
37
+ # class PostsController < ApplicationController
38
+ # content_security_policy false, only: :index
39
+ # end
40
+ def content_security_policy(enabled = true, **options, &block)
41
+ before_action(options) do
42
+ if block_given?
43
+ policy = current_content_security_policy
44
+ instance_exec(policy, &block)
45
+ request.content_security_policy = policy
46
+ end
47
+
48
+ unless enabled
49
+ request.content_security_policy = nil
50
+ end
51
+ end
52
+ end
53
+
54
+ # Overrides the globally configured `Content-Security-Policy-Report-Only`
55
+ # header:
56
+ #
57
+ # class PostsController < ApplicationController
58
+ # content_security_policy_report_only only: :index
59
+ # end
60
+ #
61
+ # Pass `false` to remove the `Content-Security-Policy-Report-Only` header:
62
+ #
63
+ # class PostsController < ApplicationController
64
+ # content_security_policy_report_only false, only: :index
65
+ # end
66
+ def content_security_policy_report_only(report_only = true, **options)
67
+ before_action(options) do
68
+ request.content_security_policy_report_only = report_only
69
+ end
70
+ end
71
+ end
72
+
73
+ private
74
+ def content_security_policy?
75
+ request.content_security_policy
76
+ end
77
+
78
+ def content_security_policy_nonce
79
+ request.content_security_policy_nonce
80
+ end
81
+
82
+ def current_content_security_policy
83
+ request.content_security_policy&.clone || ActionDispatch::ContentSecurityPolicy.new
84
+ end
85
+ end
86
+ end
@@ -1,15 +1,19 @@
1
- module ActionController #:nodoc:
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ module ActionController # :nodoc:
2
6
  module Cookies
3
7
  extend ActiveSupport::Concern
4
8
 
5
- include RackDelegation
6
-
7
9
  included do
8
- helper_method :cookies
10
+ helper_method :cookies if defined?(helper_method)
9
11
  end
10
12
 
11
13
  private
12
- def cookies
14
+ # The cookies for the current request. See ActionDispatch::Cookies for more
15
+ # information.
16
+ def cookies # :doc:
13
17
  request.cookie_jar
14
18
  end
15
19
  end