omg-actionpack 8.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (187) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +129 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +57 -0
  5. data/lib/abstract_controller/asset_paths.rb +14 -0
  6. data/lib/abstract_controller/base.rb +299 -0
  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 +265 -0
  10. data/lib/abstract_controller/collector.rb +44 -0
  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 +243 -0
  14. data/lib/abstract_controller/logger.rb +16 -0
  15. data/lib/abstract_controller/railties/routes_helpers.rb +25 -0
  16. data/lib/abstract_controller/rendering.rb +126 -0
  17. data/lib/abstract_controller/translation.rb +42 -0
  18. data/lib/abstract_controller/url_for.rb +37 -0
  19. data/lib/abstract_controller.rb +36 -0
  20. data/lib/action_controller/api/api_rendering.rb +18 -0
  21. data/lib/action_controller/api.rb +155 -0
  22. data/lib/action_controller/base.rb +332 -0
  23. data/lib/action_controller/caching.rb +49 -0
  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 +96 -0
  27. data/lib/action_controller/metal/allow_browser.rb +123 -0
  28. data/lib/action_controller/metal/basic_implicit_render.rb +17 -0
  29. data/lib/action_controller/metal/conditional_get.rb +341 -0
  30. data/lib/action_controller/metal/content_security_policy.rb +86 -0
  31. data/lib/action_controller/metal/cookies.rb +20 -0
  32. data/lib/action_controller/metal/data_streaming.rb +154 -0
  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 +59 -0
  36. data/lib/action_controller/metal/exceptions.rb +106 -0
  37. data/lib/action_controller/metal/flash.rb +67 -0
  38. data/lib/action_controller/metal/head.rb +67 -0
  39. data/lib/action_controller/metal/helpers.rb +129 -0
  40. data/lib/action_controller/metal/http_authentication.rb +565 -0
  41. data/lib/action_controller/metal/implicit_render.rb +67 -0
  42. data/lib/action_controller/metal/instrumentation.rb +120 -0
  43. data/lib/action_controller/metal/live.rb +398 -0
  44. data/lib/action_controller/metal/logging.rb +22 -0
  45. data/lib/action_controller/metal/mime_responds.rb +337 -0
  46. data/lib/action_controller/metal/parameter_encoding.rb +84 -0
  47. data/lib/action_controller/metal/params_wrapper.rb +312 -0
  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 +251 -0
  51. data/lib/action_controller/metal/renderers.rb +181 -0
  52. data/lib/action_controller/metal/rendering.rb +260 -0
  53. data/lib/action_controller/metal/request_forgery_protection.rb +667 -0
  54. data/lib/action_controller/metal/rescue.rb +33 -0
  55. data/lib/action_controller/metal/streaming.rb +183 -0
  56. data/lib/action_controller/metal/strong_parameters.rb +1546 -0
  57. data/lib/action_controller/metal/testing.rb +25 -0
  58. data/lib/action_controller/metal/url_for.rb +65 -0
  59. data/lib/action_controller/metal.rb +339 -0
  60. data/lib/action_controller/railtie.rb +149 -0
  61. data/lib/action_controller/railties/helpers.rb +26 -0
  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 +691 -0
  65. data/lib/action_controller.rb +80 -0
  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 +249 -0
  69. data/lib/action_dispatch/http/content_disposition.rb +47 -0
  70. data/lib/action_dispatch/http/content_security_policy.rb +365 -0
  71. data/lib/action_dispatch/http/filter_parameters.rb +80 -0
  72. data/lib/action_dispatch/http/filter_redirect.rb +50 -0
  73. data/lib/action_dispatch/http/headers.rb +134 -0
  74. data/lib/action_dispatch/http/mime_negotiation.rb +187 -0
  75. data/lib/action_dispatch/http/mime_type.rb +389 -0
  76. data/lib/action_dispatch/http/mime_types.rb +54 -0
  77. data/lib/action_dispatch/http/parameters.rb +119 -0
  78. data/lib/action_dispatch/http/permissions_policy.rb +189 -0
  79. data/lib/action_dispatch/http/rack_cache.rb +67 -0
  80. data/lib/action_dispatch/http/request.rb +498 -0
  81. data/lib/action_dispatch/http/response.rb +556 -0
  82. data/lib/action_dispatch/http/upload.rb +107 -0
  83. data/lib/action_dispatch/http/url.rb +344 -0
  84. data/lib/action_dispatch/journey/formatter.rb +226 -0
  85. data/lib/action_dispatch/journey/gtg/builder.rb +149 -0
  86. data/lib/action_dispatch/journey/gtg/simulator.rb +50 -0
  87. data/lib/action_dispatch/journey/gtg/transition_table.rb +217 -0
  88. data/lib/action_dispatch/journey/nfa/dot.rb +27 -0
  89. data/lib/action_dispatch/journey/nodes/node.rb +208 -0
  90. data/lib/action_dispatch/journey/parser.rb +103 -0
  91. data/lib/action_dispatch/journey/path/pattern.rb +209 -0
  92. data/lib/action_dispatch/journey/route.rb +189 -0
  93. data/lib/action_dispatch/journey/router/utils.rb +105 -0
  94. data/lib/action_dispatch/journey/router.rb +151 -0
  95. data/lib/action_dispatch/journey/routes.rb +82 -0
  96. data/lib/action_dispatch/journey/scanner.rb +70 -0
  97. data/lib/action_dispatch/journey/visitors.rb +267 -0
  98. data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
  99. data/lib/action_dispatch/journey/visualizer/fsm.js +159 -0
  100. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  101. data/lib/action_dispatch/journey.rb +7 -0
  102. data/lib/action_dispatch/log_subscriber.rb +25 -0
  103. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  104. data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
  105. data/lib/action_dispatch/middleware/callbacks.rb +38 -0
  106. data/lib/action_dispatch/middleware/cookies.rb +719 -0
  107. data/lib/action_dispatch/middleware/debug_exceptions.rb +206 -0
  108. data/lib/action_dispatch/middleware/debug_locks.rb +129 -0
  109. data/lib/action_dispatch/middleware/debug_view.rb +73 -0
  110. data/lib/action_dispatch/middleware/exception_wrapper.rb +350 -0
  111. data/lib/action_dispatch/middleware/executor.rb +32 -0
  112. data/lib/action_dispatch/middleware/flash.rb +318 -0
  113. data/lib/action_dispatch/middleware/host_authorization.rb +171 -0
  114. data/lib/action_dispatch/middleware/public_exceptions.rb +64 -0
  115. data/lib/action_dispatch/middleware/reloader.rb +16 -0
  116. data/lib/action_dispatch/middleware/remote_ip.rb +199 -0
  117. data/lib/action_dispatch/middleware/request_id.rb +50 -0
  118. data/lib/action_dispatch/middleware/server_timing.rb +78 -0
  119. data/lib/action_dispatch/middleware/session/abstract_store.rb +112 -0
  120. data/lib/action_dispatch/middleware/session/cache_store.rb +66 -0
  121. data/lib/action_dispatch/middleware/session/cookie_store.rb +129 -0
  122. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +34 -0
  123. data/lib/action_dispatch/middleware/show_exceptions.rb +88 -0
  124. data/lib/action_dispatch/middleware/ssl.rb +180 -0
  125. data/lib/action_dispatch/middleware/stack.rb +194 -0
  126. data/lib/action_dispatch/middleware/static.rb +192 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  128. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +17 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
  132. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +36 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +62 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
  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 +35 -0
  139. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  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 +284 -0
  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 +11 -0
  146. data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
  147. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
  148. data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
  149. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
  150. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
  151. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
  152. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
  153. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +19 -0
  154. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +232 -0
  155. data/lib/action_dispatch/railtie.rb +77 -0
  156. data/lib/action_dispatch/request/session.rb +283 -0
  157. data/lib/action_dispatch/request/utils.rb +109 -0
  158. data/lib/action_dispatch/routing/endpoint.rb +19 -0
  159. data/lib/action_dispatch/routing/inspector.rb +323 -0
  160. data/lib/action_dispatch/routing/mapper.rb +2372 -0
  161. data/lib/action_dispatch/routing/polymorphic_routes.rb +363 -0
  162. data/lib/action_dispatch/routing/redirection.rb +218 -0
  163. data/lib/action_dispatch/routing/route_set.rb +958 -0
  164. data/lib/action_dispatch/routing/routes_proxy.rb +66 -0
  165. data/lib/action_dispatch/routing/url_for.rb +244 -0
  166. data/lib/action_dispatch/routing.rb +262 -0
  167. data/lib/action_dispatch/system_test_case.rb +206 -0
  168. data/lib/action_dispatch/system_testing/browser.rb +75 -0
  169. data/lib/action_dispatch/system_testing/driver.rb +85 -0
  170. data/lib/action_dispatch/system_testing/server.rb +33 -0
  171. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +164 -0
  172. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +23 -0
  173. data/lib/action_dispatch/testing/assertion_response.rb +48 -0
  174. data/lib/action_dispatch/testing/assertions/response.rb +114 -0
  175. data/lib/action_dispatch/testing/assertions/routing.rb +343 -0
  176. data/lib/action_dispatch/testing/assertions.rb +25 -0
  177. data/lib/action_dispatch/testing/integration.rb +694 -0
  178. data/lib/action_dispatch/testing/request_encoder.rb +60 -0
  179. data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
  180. data/lib/action_dispatch/testing/test_process.rb +57 -0
  181. data/lib/action_dispatch/testing/test_request.rb +73 -0
  182. data/lib/action_dispatch/testing/test_response.rb +58 -0
  183. data/lib/action_dispatch.rb +147 -0
  184. data/lib/action_pack/gem_version.rb +19 -0
  185. data/lib/action_pack/version.rb +12 -0
  186. data/lib/action_pack.rb +27 -0
  187. metadata +375 -0
@@ -0,0 +1,341 @@
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"
7
+
8
+ module ActionController
9
+ module ConditionalGet
10
+ extend ActiveSupport::Concern
11
+
12
+ include Head
13
+
14
+ included do
15
+ class_attribute :etaggers, default: []
16
+ end
17
+
18
+ module ClassMethods
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.
23
+ #
24
+ # class InvoicesController < ApplicationController
25
+ # etag { current_user&.id }
26
+ #
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
32
+ # end
33
+ def etag(&etagger)
34
+ self.etaggers += [etagger]
35
+ end
36
+ end
37
+
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.
45
+ #
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.
50
+ #
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.
55
+ #
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.
60
+ #
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.
65
+ #
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`.
70
+ #
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.
75
+ #
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).
81
+ #
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.
88
+ #
89
+ #
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.
100
+ #
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
118
+ #
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
149
+ end
150
+
151
+ response.last_modified = last_modified if last_modified
152
+ response.cache_control[:public] = true if public
153
+ response.cache_control.merge!(cache_control)
154
+
155
+ head :not_modified if request.fresh?(response)
156
+ end
157
+
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
164
+ #
165
+ # See #fresh_when for supported options.
166
+ #
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
179
+ #
180
+ # You can also just pass a record:
181
+ #
182
+ # def show
183
+ # @article = Article.find(params[:id])
184
+ #
185
+ # if stale?(@article)
186
+ # @statistics = @article.really_expensive_call
187
+ # respond_to do |format|
188
+ # # all the supported formats
189
+ # end
190
+ # end
191
+ # end
192
+ #
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:
198
+ #
199
+ # def index
200
+ # @articles = Article.all
201
+ #
202
+ # if stale?(@articles)
203
+ # @statistics = @articles.really_expensive_call
204
+ # respond_to do |format|
205
+ # # all the supported formats
206
+ # end
207
+ # end
208
+ # end
209
+ #
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).
213
+ #
214
+ # When passing a record or a collection, you can still specify other options,
215
+ # such as `:public` and `:cache_control`:
216
+ #
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
225
+ # end
226
+ # end
227
+ #
228
+ # The above will set `Cache-Control: public, no-cache` in the response.
229
+ #
230
+ # When rendering a different template than the controller/action's default
231
+ # template, you can indicate which digest to include in the ETag:
232
+ #
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)
239
+ !request.fresh?(response)
240
+ end
241
+
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
+ # `:immutable`
264
+ # : If true, adds the `immutable` directive.
265
+ #
266
+ #
267
+ # Any additional key-value pairs are concatenated as directives. For a list of
268
+ # supported `Cache-Control` directives, see the [article on
269
+ # MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control).
270
+ #
271
+ # #### Examples
272
+ #
273
+ # expires_in 10.minutes
274
+ # # => Cache-Control: max-age=600, private
275
+ #
276
+ # expires_in 10.minutes, public: true
277
+ # # => Cache-Control: max-age=600, public
278
+ #
279
+ # expires_in 10.minutes, public: true, must_revalidate: true
280
+ # # => Cache-Control: max-age=600, public, must-revalidate
281
+ #
282
+ # expires_in 1.hour, stale_while_revalidate: 60.seconds
283
+ # # => Cache-Control: max-age=3600, private, stale-while-revalidate=60
284
+ #
285
+ # expires_in 1.hour, stale_if_error: 5.minutes
286
+ # # => Cache-Control: max-age=3600, private, stale-if-error=300
287
+ #
288
+ # expires_in 1.hour, public: true, "s-maxage": 3.hours, "no-transform": true
289
+ # # => Cache-Control: max-age=3600, public, s-maxage=10800, no-transform=true
290
+ #
291
+ def expires_in(seconds, options = {})
292
+ response.cache_control.delete(:no_store)
293
+ response.cache_control.merge!(
294
+ max_age: seconds,
295
+ public: options.delete(:public),
296
+ must_revalidate: options.delete(:must_revalidate),
297
+ stale_while_revalidate: options.delete(:stale_while_revalidate),
298
+ stale_if_error: options.delete(:stale_if_error),
299
+ immutable: options.delete(:immutable),
300
+ )
301
+ options.delete(:private)
302
+
303
+ response.cache_control[:extras] = options.map { |k, v| "#{k}=#{v}" }
304
+ response.date = Time.now unless response.date?
305
+ end
306
+
307
+ # Sets an HTTP 1.1 `Cache-Control` header of `no-cache`. This means the resource
308
+ # will be marked as stale, so clients must always revalidate.
309
+ # Intermediate/browser caches may still store the asset.
310
+ def expires_now
311
+ response.cache_control.replace(no_cache: true)
312
+ end
313
+
314
+ # Cache or yield the block. The cache is supposed to never expire.
315
+ #
316
+ # You can use this method when you have an HTTP response that never changes, and
317
+ # the browser and proxies should cache it indefinitely.
318
+ #
319
+ # * `public`: By default, HTTP responses are private, cached only on the
320
+ # user's web browser. To allow proxies to cache the response, set `true` to
321
+ # indicate that they can serve the cached response to all users.
322
+ def http_cache_forever(public: false)
323
+ expires_in 100.years, public: public, immutable: true
324
+
325
+ yield if stale?(etag: request.fullpath,
326
+ last_modified: Time.new(2011, 1, 1).utc,
327
+ public: public)
328
+ end
329
+
330
+ # Sets an HTTP 1.1 `Cache-Control` header of `no-store`. This means the resource
331
+ # may not be stored in any cache.
332
+ def no_store
333
+ response.cache_control.replace(no_store: true)
334
+ end
335
+
336
+ private
337
+ def combine_etags(validator, options)
338
+ [validator, *etaggers.map { |etagger| instance_exec(options, &etagger) }].compact
339
+ end
340
+ end
341
+ 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
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ module ActionController # :nodoc:
6
+ module Cookies
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ helper_method :cookies if defined?(helper_method)
11
+ end
12
+
13
+ private
14
+ # The cookies for the current request. See ActionDispatch::Cookies for more
15
+ # information.
16
+ def cookies # :doc:
17
+ request.cookie_jar
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "action_controller/metal/exceptions"
6
+ require "action_dispatch/http/content_disposition"
7
+
8
+ module ActionController # :nodoc:
9
+ # # Action Controller Data Streaming
10
+ #
11
+ # Methods for sending arbitrary data and for streaming files to the browser,
12
+ # instead of rendering.
13
+ module DataStreaming
14
+ extend ActiveSupport::Concern
15
+
16
+ include ActionController::Rendering
17
+
18
+ DEFAULT_SEND_FILE_TYPE = "application/octet-stream" # :nodoc:
19
+ DEFAULT_SEND_FILE_DISPOSITION = "attachment" # :nodoc:
20
+
21
+ private
22
+ # Sends the file. This uses a server-appropriate method (such as `X-Sendfile`)
23
+ # via the `Rack::Sendfile` middleware. The header to use is set via
24
+ # `config.action_dispatch.x_sendfile_header`. Your server can also configure
25
+ # this for you by setting the `X-Sendfile-Type` header.
26
+ #
27
+ # Be careful to sanitize the path parameter if it is coming from a web page.
28
+ # `send_file(params[:path])` allows a malicious user to download any file on
29
+ # your server.
30
+ #
31
+ # Options:
32
+ # * `:filename` - suggests a filename for the browser to use. Defaults to
33
+ # `File.basename(path)`.
34
+ # * `:type` - specifies an HTTP content type. You can specify either a string
35
+ # or a symbol for a registered type with `Mime::Type.register`, for example
36
+ # `:json`. If omitted, the type will be inferred from the file extension
37
+ # specified in `:filename`. If no content type is registered for the
38
+ # extension, the default type `application/octet-stream` will be used.
39
+ # * `:disposition` - specifies whether the file will be shown inline or
40
+ # downloaded. Valid values are `"inline"` and `"attachment"` (default).
41
+ # * `:status` - specifies the status code to send with the response. Defaults
42
+ # to 200.
43
+ # * `:url_based_filename` - set to `true` if you want the browser to guess the
44
+ # filename from the URL, which is necessary for i18n filenames on certain
45
+ # browsers (setting `:filename` overrides this option).
46
+ #
47
+ #
48
+ # The default `Content-Type` and `Content-Disposition` headers are set to
49
+ # download arbitrary binary files in as many browsers as possible. IE versions
50
+ # 4, 5, 5.5, and 6 are all known to have a variety of quirks (especially when
51
+ # downloading over SSL).
52
+ #
53
+ # Simple download:
54
+ #
55
+ # send_file '/path/to.zip'
56
+ #
57
+ # Show a JPEG in the browser:
58
+ #
59
+ # send_file '/path/to.jpeg', type: 'image/jpeg', disposition: 'inline'
60
+ #
61
+ # Show a 404 page in the browser:
62
+ #
63
+ # send_file '/path/to/404.html', type: 'text/html; charset=utf-8', disposition: 'inline', status: 404
64
+ #
65
+ # You can use other `Content-*` HTTP headers to provide additional information
66
+ # to the client. See MDN for a [list of HTTP
67
+ # headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers).
68
+ #
69
+ # Also be aware that the document may be cached by proxies and browsers. The
70
+ # `Pragma` and `Cache-Control` headers declare how the file may be cached by
71
+ # intermediaries. They default to require clients to validate with the server
72
+ # before releasing cached responses. See https://www.mnot.net/cache_docs/ for an
73
+ # overview of web caching and [RFC
74
+ # 9111](https://www.rfc-editor.org/rfc/rfc9111.html#name-cache-control) for the
75
+ # `Cache-Control` header spec.
76
+ def send_file(path, options = {}) # :doc:
77
+ raise MissingFile, "Cannot read file #{path}" unless File.file?(path) && File.readable?(path)
78
+
79
+ options[:filename] ||= File.basename(path) unless options[:url_based_filename]
80
+ send_file_headers! options
81
+
82
+ self.status = options[:status] || 200
83
+ self.content_type = options[:content_type] if options.key?(:content_type)
84
+ response.send_file path
85
+ end
86
+
87
+ # Sends the given binary data to the browser. This method is similar to `render
88
+ # plain: data`, but also allows you to specify whether the browser should
89
+ # display the response as a file attachment (i.e. in a download dialog) or as
90
+ # inline data. You may also set the content type, the file name, and other
91
+ # things.
92
+ #
93
+ # Options:
94
+ # * `:filename` - suggests a filename for the browser to use.
95
+ # * `:type` - specifies an HTTP content type. Defaults to
96
+ # `application/octet-stream`. You can specify either a string or a symbol
97
+ # for a registered type with `Mime::Type.register`, for example `:json`. If
98
+ # omitted, type will be inferred from the file extension specified in
99
+ # `:filename`. If no content type is registered for the extension, the
100
+ # default type `application/octet-stream` will be used.
101
+ # * `:disposition` - specifies whether the file will be shown inline or
102
+ # downloaded. Valid values are `"inline"` and `"attachment"` (default).
103
+ # * `:status` - specifies the status code to send with the response. Defaults
104
+ # to 200.
105
+ #
106
+ #
107
+ # Generic data download:
108
+ #
109
+ # send_data buffer
110
+ #
111
+ # Download a dynamically-generated tarball:
112
+ #
113
+ # send_data generate_tgz('dir'), filename: 'dir.tgz'
114
+ #
115
+ # Display an image Active Record in the browser:
116
+ #
117
+ # send_data image.data, type: image.content_type, disposition: 'inline'
118
+ #
119
+ # See `send_file` for more information on HTTP `Content-*` headers and caching.
120
+ def send_data(data, options = {}) # :doc:
121
+ send_file_headers! options
122
+ render options.slice(:status, :content_type).merge(body: data)
123
+ end
124
+
125
+ def send_file_headers!(options)
126
+ type_provided = options.has_key?(:type)
127
+
128
+ content_type = options.fetch(:type, DEFAULT_SEND_FILE_TYPE)
129
+ self.content_type = content_type
130
+ response.sending_file = true
131
+
132
+ raise ArgumentError, ":type option required" if content_type.nil?
133
+
134
+ if content_type.is_a?(Symbol)
135
+ extension = Mime[content_type]
136
+ raise ArgumentError, "Unknown MIME type #{options[:type]}" unless extension
137
+ self.content_type = extension
138
+ else
139
+ if !type_provided && options[:filename]
140
+ # If type wasn't provided, try guessing from file extension.
141
+ content_type = Mime::Type.lookup_by_extension(File.extname(options[:filename]).downcase.delete(".")) || content_type
142
+ end
143
+ self.content_type = content_type
144
+ end
145
+
146
+ disposition = options.fetch(:disposition, DEFAULT_SEND_FILE_DISPOSITION)
147
+ if disposition
148
+ headers["Content-Disposition"] = ActionDispatch::Http::ContentDisposition.format(disposition: disposition, filename: options[:filename])
149
+ end
150
+
151
+ headers["Content-Transfer-Encoding"] = "binary"
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ module ActionController
6
+ # # Action Controller Default Headers
7
+ #
8
+ # Allows configuring default headers that will be automatically merged into each
9
+ # response.
10
+ module DefaultHeaders
11
+ extend ActiveSupport::Concern
12
+
13
+ module ClassMethods
14
+ def make_response!(request)
15
+ ActionDispatch::Response.create.tap do |res|
16
+ res.request = request
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ module ActionController
6
+ # # Action Controller Etag With Flash
7
+ #
8
+ # When you're using the flash, it's generally used as a conditional on the view.
9
+ # This means the content of the view depends on the flash. Which in turn means
10
+ # that the ETag for a response should be computed with the content of the flash
11
+ # in mind. This does that by including the content of the flash as a component
12
+ # in the ETag that's generated for a response.
13
+ module EtagWithFlash
14
+ extend ActiveSupport::Concern
15
+
16
+ include ActionController::ConditionalGet
17
+
18
+ included do
19
+ etag { flash if request.respond_to?(:flash) && !flash.empty? }
20
+ end
21
+ end
22
+ end