omg-actionpack 8.0.0.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +129 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +57 -0
- data/lib/abstract_controller/asset_paths.rb +14 -0
- data/lib/abstract_controller/base.rb +299 -0
- data/lib/abstract_controller/caching/fragments.rb +149 -0
- data/lib/abstract_controller/caching.rb +68 -0
- data/lib/abstract_controller/callbacks.rb +265 -0
- data/lib/abstract_controller/collector.rb +44 -0
- data/lib/abstract_controller/deprecator.rb +9 -0
- data/lib/abstract_controller/error.rb +8 -0
- data/lib/abstract_controller/helpers.rb +243 -0
- data/lib/abstract_controller/logger.rb +16 -0
- data/lib/abstract_controller/railties/routes_helpers.rb +25 -0
- data/lib/abstract_controller/rendering.rb +126 -0
- data/lib/abstract_controller/translation.rb +42 -0
- data/lib/abstract_controller/url_for.rb +37 -0
- data/lib/abstract_controller.rb +36 -0
- data/lib/action_controller/api/api_rendering.rb +18 -0
- data/lib/action_controller/api.rb +155 -0
- data/lib/action_controller/base.rb +332 -0
- data/lib/action_controller/caching.rb +49 -0
- data/lib/action_controller/deprecator.rb +9 -0
- data/lib/action_controller/form_builder.rb +55 -0
- data/lib/action_controller/log_subscriber.rb +96 -0
- data/lib/action_controller/metal/allow_browser.rb +123 -0
- data/lib/action_controller/metal/basic_implicit_render.rb +17 -0
- data/lib/action_controller/metal/conditional_get.rb +341 -0
- data/lib/action_controller/metal/content_security_policy.rb +86 -0
- data/lib/action_controller/metal/cookies.rb +20 -0
- data/lib/action_controller/metal/data_streaming.rb +154 -0
- data/lib/action_controller/metal/default_headers.rb +21 -0
- data/lib/action_controller/metal/etag_with_flash.rb +22 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +59 -0
- data/lib/action_controller/metal/exceptions.rb +106 -0
- data/lib/action_controller/metal/flash.rb +67 -0
- data/lib/action_controller/metal/head.rb +67 -0
- data/lib/action_controller/metal/helpers.rb +129 -0
- data/lib/action_controller/metal/http_authentication.rb +565 -0
- data/lib/action_controller/metal/implicit_render.rb +67 -0
- data/lib/action_controller/metal/instrumentation.rb +120 -0
- data/lib/action_controller/metal/live.rb +398 -0
- data/lib/action_controller/metal/logging.rb +22 -0
- data/lib/action_controller/metal/mime_responds.rb +337 -0
- data/lib/action_controller/metal/parameter_encoding.rb +84 -0
- data/lib/action_controller/metal/params_wrapper.rb +312 -0
- data/lib/action_controller/metal/permissions_policy.rb +38 -0
- data/lib/action_controller/metal/rate_limiting.rb +62 -0
- data/lib/action_controller/metal/redirecting.rb +251 -0
- data/lib/action_controller/metal/renderers.rb +181 -0
- data/lib/action_controller/metal/rendering.rb +260 -0
- data/lib/action_controller/metal/request_forgery_protection.rb +667 -0
- data/lib/action_controller/metal/rescue.rb +33 -0
- data/lib/action_controller/metal/streaming.rb +183 -0
- data/lib/action_controller/metal/strong_parameters.rb +1546 -0
- data/lib/action_controller/metal/testing.rb +25 -0
- data/lib/action_controller/metal/url_for.rb +65 -0
- data/lib/action_controller/metal.rb +339 -0
- data/lib/action_controller/railtie.rb +149 -0
- data/lib/action_controller/railties/helpers.rb +26 -0
- data/lib/action_controller/renderer.rb +161 -0
- data/lib/action_controller/template_assertions.rb +13 -0
- data/lib/action_controller/test_case.rb +691 -0
- data/lib/action_controller.rb +80 -0
- data/lib/action_dispatch/constants.rb +34 -0
- data/lib/action_dispatch/deprecator.rb +9 -0
- data/lib/action_dispatch/http/cache.rb +249 -0
- data/lib/action_dispatch/http/content_disposition.rb +47 -0
- data/lib/action_dispatch/http/content_security_policy.rb +365 -0
- data/lib/action_dispatch/http/filter_parameters.rb +80 -0
- data/lib/action_dispatch/http/filter_redirect.rb +50 -0
- data/lib/action_dispatch/http/headers.rb +134 -0
- data/lib/action_dispatch/http/mime_negotiation.rb +187 -0
- data/lib/action_dispatch/http/mime_type.rb +389 -0
- data/lib/action_dispatch/http/mime_types.rb +54 -0
- data/lib/action_dispatch/http/parameters.rb +119 -0
- data/lib/action_dispatch/http/permissions_policy.rb +189 -0
- data/lib/action_dispatch/http/rack_cache.rb +67 -0
- data/lib/action_dispatch/http/request.rb +498 -0
- data/lib/action_dispatch/http/response.rb +556 -0
- data/lib/action_dispatch/http/upload.rb +107 -0
- data/lib/action_dispatch/http/url.rb +344 -0
- data/lib/action_dispatch/journey/formatter.rb +226 -0
- data/lib/action_dispatch/journey/gtg/builder.rb +149 -0
- data/lib/action_dispatch/journey/gtg/simulator.rb +50 -0
- data/lib/action_dispatch/journey/gtg/transition_table.rb +217 -0
- data/lib/action_dispatch/journey/nfa/dot.rb +27 -0
- data/lib/action_dispatch/journey/nodes/node.rb +208 -0
- data/lib/action_dispatch/journey/parser.rb +103 -0
- data/lib/action_dispatch/journey/path/pattern.rb +209 -0
- data/lib/action_dispatch/journey/route.rb +189 -0
- data/lib/action_dispatch/journey/router/utils.rb +105 -0
- data/lib/action_dispatch/journey/router.rb +151 -0
- data/lib/action_dispatch/journey/routes.rb +82 -0
- data/lib/action_dispatch/journey/scanner.rb +70 -0
- data/lib/action_dispatch/journey/visitors.rb +267 -0
- data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
- data/lib/action_dispatch/journey/visualizer/fsm.js +159 -0
- data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
- data/lib/action_dispatch/journey.rb +7 -0
- data/lib/action_dispatch/log_subscriber.rb +25 -0
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
- data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
- data/lib/action_dispatch/middleware/callbacks.rb +38 -0
- data/lib/action_dispatch/middleware/cookies.rb +719 -0
- data/lib/action_dispatch/middleware/debug_exceptions.rb +206 -0
- data/lib/action_dispatch/middleware/debug_locks.rb +129 -0
- data/lib/action_dispatch/middleware/debug_view.rb +73 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +350 -0
- data/lib/action_dispatch/middleware/executor.rb +32 -0
- data/lib/action_dispatch/middleware/flash.rb +318 -0
- data/lib/action_dispatch/middleware/host_authorization.rb +171 -0
- data/lib/action_dispatch/middleware/public_exceptions.rb +64 -0
- data/lib/action_dispatch/middleware/reloader.rb +16 -0
- data/lib/action_dispatch/middleware/remote_ip.rb +199 -0
- data/lib/action_dispatch/middleware/request_id.rb +50 -0
- data/lib/action_dispatch/middleware/server_timing.rb +78 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +112 -0
- data/lib/action_dispatch/middleware/session/cache_store.rb +66 -0
- data/lib/action_dispatch/middleware/session/cookie_store.rb +129 -0
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +34 -0
- data/lib/action_dispatch/middleware/show_exceptions.rb +88 -0
- data/lib/action_dispatch/middleware/ssl.rb +180 -0
- data/lib/action_dispatch/middleware/stack.rb +194 -0
- data/lib/action_dispatch/middleware/static.rb +192 -0
- data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
- data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +17 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +36 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +62 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +12 -0
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +35 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +16 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +284 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +23 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +19 -0
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +232 -0
- data/lib/action_dispatch/railtie.rb +77 -0
- data/lib/action_dispatch/request/session.rb +283 -0
- data/lib/action_dispatch/request/utils.rb +109 -0
- data/lib/action_dispatch/routing/endpoint.rb +19 -0
- data/lib/action_dispatch/routing/inspector.rb +323 -0
- data/lib/action_dispatch/routing/mapper.rb +2372 -0
- data/lib/action_dispatch/routing/polymorphic_routes.rb +363 -0
- data/lib/action_dispatch/routing/redirection.rb +218 -0
- data/lib/action_dispatch/routing/route_set.rb +958 -0
- data/lib/action_dispatch/routing/routes_proxy.rb +66 -0
- data/lib/action_dispatch/routing/url_for.rb +244 -0
- data/lib/action_dispatch/routing.rb +262 -0
- data/lib/action_dispatch/system_test_case.rb +206 -0
- data/lib/action_dispatch/system_testing/browser.rb +75 -0
- data/lib/action_dispatch/system_testing/driver.rb +85 -0
- data/lib/action_dispatch/system_testing/server.rb +33 -0
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +164 -0
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +23 -0
- data/lib/action_dispatch/testing/assertion_response.rb +48 -0
- data/lib/action_dispatch/testing/assertions/response.rb +114 -0
- data/lib/action_dispatch/testing/assertions/routing.rb +343 -0
- data/lib/action_dispatch/testing/assertions.rb +25 -0
- data/lib/action_dispatch/testing/integration.rb +694 -0
- data/lib/action_dispatch/testing/request_encoder.rb +60 -0
- data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
- data/lib/action_dispatch/testing/test_process.rb +57 -0
- data/lib/action_dispatch/testing/test_request.rb +73 -0
- data/lib/action_dispatch/testing/test_response.rb +58 -0
- data/lib/action_dispatch.rb +147 -0
- data/lib/action_pack/gem_version.rb +19 -0
- data/lib/action_pack/version.rb +12 -0
- data/lib/action_pack.rb +27 -0
- 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
|