omg-actionpack 8.0.0.alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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