actionpack 4.2.11.1 → 6.1.3.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (187) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +291 -489
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +9 -9
  5. data/lib/abstract_controller/asset_paths.rb +2 -0
  6. data/lib/abstract_controller/base.rb +81 -51
  7. data/lib/{action_controller → abstract_controller}/caching/fragments.rb +64 -17
  8. data/lib/abstract_controller/caching.rb +66 -0
  9. data/lib/abstract_controller/callbacks.rb +61 -33
  10. data/lib/abstract_controller/collector.rb +9 -13
  11. data/lib/abstract_controller/error.rb +6 -0
  12. data/lib/abstract_controller/helpers.rb +115 -99
  13. data/lib/abstract_controller/logger.rb +2 -0
  14. data/lib/abstract_controller/railties/routes_helpers.rb +21 -3
  15. data/lib/abstract_controller/rendering.rb +48 -47
  16. data/lib/abstract_controller/translation.rb +17 -8
  17. data/lib/abstract_controller/url_for.rb +2 -0
  18. data/lib/abstract_controller.rb +13 -5
  19. data/lib/action_controller/api/api_rendering.rb +16 -0
  20. data/lib/action_controller/api.rb +150 -0
  21. data/lib/action_controller/base.rb +29 -24
  22. data/lib/action_controller/caching.rb +12 -57
  23. data/lib/action_controller/form_builder.rb +50 -0
  24. data/lib/action_controller/log_subscriber.rb +17 -19
  25. data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
  26. data/lib/action_controller/metal/conditional_get.rb +134 -46
  27. data/lib/action_controller/metal/content_security_policy.rb +51 -0
  28. data/lib/action_controller/metal/cookies.rb +6 -4
  29. data/lib/action_controller/metal/data_streaming.rb +30 -50
  30. data/lib/action_controller/metal/default_headers.rb +17 -0
  31. data/lib/action_controller/metal/etag_with_flash.rb +18 -0
  32. data/lib/action_controller/metal/etag_with_template_digest.rb +21 -16
  33. data/lib/action_controller/metal/exceptions.rb +63 -15
  34. data/lib/action_controller/metal/flash.rb +9 -8
  35. data/lib/action_controller/metal/head.rb +26 -21
  36. data/lib/action_controller/metal/helpers.rb +37 -18
  37. data/lib/action_controller/metal/http_authentication.rb +81 -73
  38. data/lib/action_controller/metal/implicit_render.rb +53 -9
  39. data/lib/action_controller/metal/instrumentation.rb +32 -35
  40. data/lib/action_controller/metal/live.rb +102 -120
  41. data/lib/action_controller/metal/logging.rb +20 -0
  42. data/lib/action_controller/metal/mime_responds.rb +49 -47
  43. data/lib/action_controller/metal/parameter_encoding.rb +82 -0
  44. data/lib/action_controller/metal/params_wrapper.rb +83 -66
  45. data/lib/action_controller/metal/permissions_policy.rb +46 -0
  46. data/lib/action_controller/metal/redirecting.rb +53 -32
  47. data/lib/action_controller/metal/renderers.rb +87 -44
  48. data/lib/action_controller/metal/rendering.rb +77 -50
  49. data/lib/action_controller/metal/request_forgery_protection.rb +267 -103
  50. data/lib/action_controller/metal/rescue.rb +10 -17
  51. data/lib/action_controller/metal/streaming.rb +12 -11
  52. data/lib/action_controller/metal/strong_parameters.rb +714 -186
  53. data/lib/action_controller/metal/testing.rb +2 -17
  54. data/lib/action_controller/metal/url_for.rb +19 -10
  55. data/lib/action_controller/metal.rb +104 -87
  56. data/lib/action_controller/railtie.rb +28 -10
  57. data/lib/action_controller/railties/helpers.rb +3 -1
  58. data/lib/action_controller/renderer.rb +141 -0
  59. data/lib/action_controller/template_assertions.rb +11 -0
  60. data/lib/action_controller/test_case.rb +296 -422
  61. data/lib/action_controller.rb +34 -23
  62. data/lib/action_dispatch/http/cache.rb +107 -56
  63. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  64. data/lib/action_dispatch/http/content_security_policy.rb +286 -0
  65. data/lib/action_dispatch/http/filter_parameters.rb +32 -25
  66. data/lib/action_dispatch/http/filter_redirect.rb +10 -12
  67. data/lib/action_dispatch/http/headers.rb +55 -22
  68. data/lib/action_dispatch/http/mime_negotiation.rb +79 -51
  69. data/lib/action_dispatch/http/mime_type.rb +153 -121
  70. data/lib/action_dispatch/http/mime_types.rb +20 -6
  71. data/lib/action_dispatch/http/parameters.rb +90 -40
  72. data/lib/action_dispatch/http/permissions_policy.rb +173 -0
  73. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  74. data/lib/action_dispatch/http/request.rb +226 -121
  75. data/lib/action_dispatch/http/response.rb +248 -113
  76. data/lib/action_dispatch/http/upload.rb +21 -7
  77. data/lib/action_dispatch/http/url.rb +182 -100
  78. data/lib/action_dispatch/journey/formatter.rb +90 -43
  79. data/lib/action_dispatch/journey/gtg/builder.rb +28 -41
  80. data/lib/action_dispatch/journey/gtg/simulator.rb +11 -16
  81. data/lib/action_dispatch/journey/gtg/transition_table.rb +23 -21
  82. data/lib/action_dispatch/journey/nfa/dot.rb +3 -14
  83. data/lib/action_dispatch/journey/nodes/node.rb +29 -15
  84. data/lib/action_dispatch/journey/parser.rb +17 -16
  85. data/lib/action_dispatch/journey/parser.y +4 -3
  86. data/lib/action_dispatch/journey/parser_extras.rb +12 -4
  87. data/lib/action_dispatch/journey/path/pattern.rb +58 -54
  88. data/lib/action_dispatch/journey/route.rb +100 -32
  89. data/lib/action_dispatch/journey/router/utils.rb +29 -18
  90. data/lib/action_dispatch/journey/router.rb +55 -51
  91. data/lib/action_dispatch/journey/routes.rb +17 -17
  92. data/lib/action_dispatch/journey/scanner.rb +26 -17
  93. data/lib/action_dispatch/journey/visitors.rb +98 -54
  94. data/lib/action_dispatch/journey.rb +5 -5
  95. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  96. data/lib/action_dispatch/middleware/callbacks.rb +3 -6
  97. data/lib/action_dispatch/middleware/cookies.rb +347 -217
  98. data/lib/action_dispatch/middleware/debug_exceptions.rb +135 -63
  99. data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
  100. data/lib/action_dispatch/middleware/debug_view.rb +66 -0
  101. data/lib/action_dispatch/middleware/exception_wrapper.rb +115 -71
  102. data/lib/action_dispatch/middleware/executor.rb +21 -0
  103. data/lib/action_dispatch/middleware/flash.rb +78 -54
  104. data/lib/action_dispatch/middleware/host_authorization.rb +130 -0
  105. data/lib/action_dispatch/middleware/public_exceptions.rb +32 -27
  106. data/lib/action_dispatch/middleware/reloader.rb +5 -91
  107. data/lib/action_dispatch/middleware/remote_ip.rb +53 -45
  108. data/lib/action_dispatch/middleware/request_id.rb +17 -10
  109. data/lib/action_dispatch/middleware/session/abstract_store.rb +41 -26
  110. data/lib/action_dispatch/middleware/session/cache_store.rb +24 -14
  111. data/lib/action_dispatch/middleware/session/cookie_store.rb +74 -75
  112. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -2
  113. data/lib/action_dispatch/middleware/show_exceptions.rb +28 -23
  114. data/lib/action_dispatch/middleware/ssl.rb +118 -35
  115. data/lib/action_dispatch/middleware/stack.rb +82 -41
  116. data/lib/action_dispatch/middleware/static.rb +156 -89
  117. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  119. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +4 -14
  121. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  122. data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +4 -2
  123. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  124. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +45 -35
  125. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -0
  126. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +23 -4
  128. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
  129. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +15 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +105 -8
  132. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +2 -2
  135. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -1
  136. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +3 -3
  137. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  138. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  139. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
  140. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +87 -64
  141. data/lib/action_dispatch/railtie.rb +27 -13
  142. data/lib/action_dispatch/request/session.rb +109 -61
  143. data/lib/action_dispatch/request/utils.rb +90 -23
  144. data/lib/action_dispatch/routing/endpoint.rb +9 -2
  145. data/lib/action_dispatch/routing/inspector.rb +141 -102
  146. data/lib/action_dispatch/routing/mapper.rb +811 -473
  147. data/lib/action_dispatch/routing/polymorphic_routes.rb +167 -143
  148. data/lib/action_dispatch/routing/redirection.rb +37 -27
  149. data/lib/action_dispatch/routing/route_set.rb +363 -331
  150. data/lib/action_dispatch/routing/routes_proxy.rb +32 -5
  151. data/lib/action_dispatch/routing/url_for.rb +66 -26
  152. data/lib/action_dispatch/routing.rb +36 -36
  153. data/lib/action_dispatch/system_test_case.rb +190 -0
  154. data/lib/action_dispatch/system_testing/browser.rb +86 -0
  155. data/lib/action_dispatch/system_testing/driver.rb +67 -0
  156. data/lib/action_dispatch/system_testing/server.rb +31 -0
  157. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +138 -0
  158. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +29 -0
  159. data/lib/action_dispatch/testing/assertion_response.rb +46 -0
  160. data/lib/action_dispatch/testing/assertions/response.rb +44 -22
  161. data/lib/action_dispatch/testing/assertions/routing.rb +47 -31
  162. data/lib/action_dispatch/testing/assertions.rb +6 -4
  163. data/lib/action_dispatch/testing/integration.rb +391 -220
  164. data/lib/action_dispatch/testing/request_encoder.rb +55 -0
  165. data/lib/action_dispatch/testing/test_process.rb +53 -22
  166. data/lib/action_dispatch/testing/test_request.rb +27 -34
  167. data/lib/action_dispatch/testing/test_response.rb +11 -11
  168. data/lib/action_dispatch.rb +35 -21
  169. data/lib/action_pack/gem_version.rb +6 -4
  170. data/lib/action_pack/version.rb +3 -1
  171. data/lib/action_pack.rb +4 -2
  172. metadata +78 -48
  173. data/lib/action_controller/metal/force_ssl.rb +0 -97
  174. data/lib/action_controller/metal/hide_actions.rb +0 -40
  175. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  176. data/lib/action_controller/middleware.rb +0 -39
  177. data/lib/action_controller/model_naming.rb +0 -12
  178. data/lib/action_dispatch/http/parameter_filter.rb +0 -72
  179. data/lib/action_dispatch/journey/backwards.rb +0 -5
  180. data/lib/action_dispatch/journey/nfa/builder.rb +0 -76
  181. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
  182. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -163
  183. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  184. data/lib/action_dispatch/middleware/params_parser.rb +0 -60
  185. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  186. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  187. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -1,30 +1,31 @@
1
- require 'active_support/core_ext/hash/keys'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/object/try"
4
+ require "active_support/core_ext/integer/time"
2
5
 
3
6
  module ActionController
4
7
  module ConditionalGet
5
8
  extend ActiveSupport::Concern
6
9
 
7
- include RackDelegation
8
10
  include Head
9
11
 
10
12
  included do
11
- class_attribute :etaggers
12
- self.etaggers = []
13
+ class_attribute :etaggers, default: []
13
14
  end
14
15
 
15
16
  module ClassMethods
16
17
  # Allows you to consider additional controller-wide information when generating an ETag.
17
18
  # For example, if you serve pages tailored depending on who's logged in at the moment, you
18
- # may want to add the current user id to be part of the ETag to prevent authorized displaying
19
+ # may want to add the current user id to be part of the ETag to prevent unauthorized displaying
19
20
  # of cached pages.
20
21
  #
21
22
  # class InvoicesController < ApplicationController
22
- # etag { current_user.try :id }
23
+ # etag { current_user&.id }
23
24
  #
24
25
  # def show
25
26
  # # Etag will differ even for the same invoice when it's viewed by a different current_user
26
27
  # @invoice = Invoice.find(params[:id])
27
- # fresh_when(@invoice)
28
+ # fresh_when etag: @invoice
28
29
  # end
29
30
  # end
30
31
  def etag(&etagger)
@@ -37,10 +38,25 @@ module ActionController
37
38
  #
38
39
  # === Parameters:
39
40
  #
40
- # * <tt>:etag</tt>.
41
- # * <tt>:last_modified</tt>.
41
+ # * <tt>:etag</tt> Sets a "weak" ETag validator on the response. See the
42
+ # +:weak_etag+ option.
43
+ # * <tt>:weak_etag</tt> Sets a "weak" ETag validator on the response.
44
+ # Requests that set If-None-Match header may return a 304 Not Modified
45
+ # response if it matches the ETag exactly. A weak ETag indicates semantic
46
+ # equivalence, not byte-for-byte equality, so they're good for caching
47
+ # HTML pages in browser caches. They can't be used for responses that
48
+ # must be byte-identical, like serving Range requests within a PDF file.
49
+ # * <tt>:strong_etag</tt> Sets a "strong" ETag validator on the response.
50
+ # Requests that set If-None-Match header may return a 304 Not Modified
51
+ # response if it matches the ETag exactly. A strong ETag implies exact
52
+ # equality: the response must match byte for byte. This is necessary for
53
+ # doing Range requests within a large video or PDF file, for example, or
54
+ # for compatibility with some CDNs that don't support weak ETags.
55
+ # * <tt>:last_modified</tt> Sets a "weak" last-update validator on the
56
+ # response. Subsequent requests that set If-Modified-Since may return a
57
+ # 304 Not Modified response if last_modified <= If-Modified-Since.
42
58
  # * <tt>:public</tt> By default the Cache-Control header is private, set this to
43
- # +true+ if you want your application to be cachable by other devices (proxy caches).
59
+ # +true+ if you want your application to be cacheable by other devices (proxy caches).
44
60
  # * <tt>:template</tt> By default, the template digest for the current
45
61
  # controller/action is included in ETags. If the action renders a
46
62
  # different template, you can include its digest instead. If the action
@@ -51,21 +67,31 @@ module ActionController
51
67
  #
52
68
  # def show
53
69
  # @article = Article.find(params[:id])
54
- # fresh_when(etag: @article, last_modified: @article.created_at, public: true)
70
+ # fresh_when(etag: @article, last_modified: @article.updated_at, public: true)
55
71
  # end
56
72
  #
57
73
  # This will render the show template if the request isn't sending a matching ETag or
58
74
  # If-Modified-Since header and just a <tt>304 Not Modified</tt> response if there's a match.
59
75
  #
60
- # You can also just pass a record where +last_modified+ will be set by calling
61
- # +updated_at+ and the +etag+ by passing the object itself.
76
+ # You can also just pass a record. In this case +last_modified+ will be set
77
+ # by calling +updated_at+ and +etag+ by passing the object itself.
62
78
  #
63
79
  # def show
64
80
  # @article = Article.find(params[:id])
65
81
  # fresh_when(@article)
66
82
  # end
67
83
  #
68
- # When passing a record, you can still set whether the public header:
84
+ # You can also pass an object that responds to +maximum+, such as a
85
+ # collection of active records. In this case +last_modified+ will be set by
86
+ # calling <tt>maximum(:updated_at)</tt> on the collection (the timestamp of the
87
+ # most recently updated record) and the +etag+ by passing the object itself.
88
+ #
89
+ # def index
90
+ # @articles = Article.all
91
+ # fresh_when(@articles)
92
+ # end
93
+ #
94
+ # When passing a record or a collection, you can still set the public header:
69
95
  #
70
96
  # def show
71
97
  # @article = Article.find(params[:id])
@@ -77,18 +103,20 @@ module ActionController
77
103
  #
78
104
  # before_action { fresh_when @article, template: 'widgets/show' }
79
105
  #
80
- def fresh_when(record_or_options, additional_options = {})
81
- if record_or_options.is_a? Hash
82
- options = record_or_options
83
- options.assert_valid_keys(:etag, :last_modified, :public, :template)
84
- else
85
- record = record_or_options
86
- options = { etag: record, last_modified: record.try(:updated_at) }.merge!(additional_options)
106
+ def fresh_when(object = nil, etag: nil, weak_etag: nil, strong_etag: nil, last_modified: nil, public: false, template: nil)
107
+ weak_etag ||= etag || object unless strong_etag
108
+ last_modified ||= object.try(:updated_at) || object.try(:maximum, :updated_at)
109
+
110
+ if strong_etag
111
+ response.strong_etag = combine_etags strong_etag,
112
+ last_modified: last_modified, public: public, template: template
113
+ elsif weak_etag || template
114
+ response.weak_etag = combine_etags weak_etag,
115
+ last_modified: last_modified, public: public, template: template
87
116
  end
88
117
 
89
- response.etag = combine_etags(options) if options[:etag] || options[:template]
90
- response.last_modified = options[:last_modified] if options[:last_modified]
91
- response.cache_control[:public] = true if options[:public]
118
+ response.last_modified = last_modified if last_modified
119
+ response.cache_control[:public] = true if public
92
120
 
93
121
  head :not_modified if request.fresh?(response)
94
122
  end
@@ -100,10 +128,25 @@ module ActionController
100
128
  #
101
129
  # === Parameters:
102
130
  #
103
- # * <tt>:etag</tt>.
104
- # * <tt>:last_modified</tt>.
131
+ # * <tt>:etag</tt> Sets a "weak" ETag validator on the response. See the
132
+ # +:weak_etag+ option.
133
+ # * <tt>:weak_etag</tt> Sets a "weak" ETag validator on the response.
134
+ # Requests that set If-None-Match header may return a 304 Not Modified
135
+ # response if it matches the ETag exactly. A weak ETag indicates semantic
136
+ # equivalence, not byte-for-byte equality, so they're good for caching
137
+ # HTML pages in browser caches. They can't be used for responses that
138
+ # must be byte-identical, like serving Range requests within a PDF file.
139
+ # * <tt>:strong_etag</tt> Sets a "strong" ETag validator on the response.
140
+ # Requests that set If-None-Match header may return a 304 Not Modified
141
+ # response if it matches the ETag exactly. A strong ETag implies exact
142
+ # equality: the response must match byte for byte. This is necessary for
143
+ # doing Range requests within a large video or PDF file, for example, or
144
+ # for compatibility with some CDNs that don't support weak ETags.
145
+ # * <tt>:last_modified</tt> Sets a "weak" last-update validator on the
146
+ # response. Subsequent requests that set If-Modified-Since may return a
147
+ # 304 Not Modified response if last_modified <= If-Modified-Since.
105
148
  # * <tt>:public</tt> By default the Cache-Control header is private, set this to
106
- # +true+ if you want your application to be cachable by other devices (proxy caches).
149
+ # +true+ if you want your application to be cacheable by other devices (proxy caches).
107
150
  # * <tt>:template</tt> By default, the template digest for the current
108
151
  # controller/action is included in ETags. If the action renders a
109
152
  # different template, you can include its digest instead. If the action
@@ -115,7 +158,7 @@ module ActionController
115
158
  # def show
116
159
  # @article = Article.find(params[:id])
117
160
  #
118
- # if stale?(etag: @article, last_modified: @article.created_at)
161
+ # if stale?(etag: @article, last_modified: @article.updated_at)
119
162
  # @statistics = @article.really_expensive_call
120
163
  # respond_to do |format|
121
164
  # # all the supported formats
@@ -123,8 +166,8 @@ module ActionController
123
166
  # end
124
167
  # end
125
168
  #
126
- # You can also just pass a record where +last_modified+ will be set by calling
127
- # +updated_at+ and the +etag+ by passing the object itself.
169
+ # You can also just pass a record. In this case +last_modified+ will be set
170
+ # by calling +updated_at+ and +etag+ by passing the object itself.
128
171
  #
129
172
  # def show
130
173
  # @article = Article.find(params[:id])
@@ -137,7 +180,23 @@ module ActionController
137
180
  # end
138
181
  # end
139
182
  #
140
- # When passing a record, you can still set whether the public header:
183
+ # You can also pass an object that responds to +maximum+, such as a
184
+ # collection of active records. In this case +last_modified+ will be set by
185
+ # calling <tt>maximum(:updated_at)</tt> on the collection (the timestamp of the
186
+ # most recently updated record) and the +etag+ by passing the object itself.
187
+ #
188
+ # def index
189
+ # @articles = Article.all
190
+ #
191
+ # if stale?(@articles)
192
+ # @statistics = @articles.really_expensive_call
193
+ # respond_to do |format|
194
+ # # all the supported formats
195
+ # end
196
+ # end
197
+ # end
198
+ #
199
+ # When passing a record or a collection, you can still set the public header:
141
200
  #
142
201
  # def show
143
202
  # @article = Article.find(params[:id])
@@ -157,12 +216,12 @@ module ActionController
157
216
  # super if stale? @article, template: 'widgets/show'
158
217
  # end
159
218
  #
160
- def stale?(record_or_options, additional_options = {})
161
- fresh_when(record_or_options, additional_options)
219
+ def stale?(object = nil, **freshness_kwargs)
220
+ fresh_when(object, **freshness_kwargs)
162
221
  !request.fresh?(response)
163
222
  end
164
223
 
165
- # Sets a HTTP 1.1 Cache-Control header. Defaults to issuing a +private+
224
+ # Sets an HTTP 1.1 Cache-Control header. Defaults to issuing a +private+
166
225
  # instruction, so that intermediate caches must not cache the response.
167
226
  #
168
227
  # expires_in 20.minutes
@@ -170,31 +229,60 @@ module ActionController
170
229
  # expires_in 3.hours, public: true, must_revalidate: true
171
230
  #
172
231
  # This method will overwrite an existing Cache-Control header.
173
- # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
232
+ # See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
233
+ #
234
+ # HTTP Cache-Control Extensions for Stale Content. See https://tools.ietf.org/html/rfc5861
235
+ # It helps to cache an asset and serve it while is being revalidated and/or returning with an error.
236
+ #
237
+ # expires_in 3.hours, public: true, stale_while_revalidate: 60.seconds
238
+ # expires_in 3.hours, public: true, stale_while_revalidate: 60.seconds, stale_if_error: 5.minutes
239
+ #
240
+ # HTTP Cache-Control Extensions other values: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
241
+ # Any additional key-value pairs are concatenated onto the `Cache-Control` header in the response:
242
+ #
243
+ # expires_in 3.hours, public: true, "s-maxage": 3.hours, "no-transform": true
174
244
  #
175
- # The method will also ensure a HTTP Date header for client compatibility.
245
+ # The method will also ensure an HTTP Date header for client compatibility.
176
246
  def expires_in(seconds, options = {})
177
247
  response.cache_control.merge!(
178
- :max_age => seconds,
179
- :public => options.delete(:public),
180
- :must_revalidate => options.delete(:must_revalidate)
248
+ max_age: seconds,
249
+ public: options.delete(:public),
250
+ must_revalidate: options.delete(:must_revalidate),
251
+ stale_while_revalidate: options.delete(:stale_while_revalidate),
252
+ stale_if_error: options.delete(:stale_if_error),
181
253
  )
182
254
  options.delete(:private)
183
255
 
184
- response.cache_control[:extras] = options.map {|k,v| "#{k}=#{v}"}
256
+ response.cache_control[:extras] = options.map { |k, v| "#{k}=#{v}" }
185
257
  response.date = Time.now unless response.date?
186
258
  end
187
259
 
188
- # Sets a HTTP 1.1 Cache-Control header of <tt>no-cache</tt> so no caching should
189
- # occur by the browser or intermediate caches (like caching proxy servers).
260
+ # Sets an HTTP 1.1 Cache-Control header of <tt>no-cache</tt>. This means the
261
+ # resource will be marked as stale, so clients must always revalidate.
262
+ # Intermediate/browser caches may still store the asset.
190
263
  def expires_now
191
- response.cache_control.replace(:no_cache => true)
264
+ response.cache_control.replace(no_cache: true)
265
+ end
266
+
267
+ # Cache or yield the block. The cache is supposed to never expire.
268
+ #
269
+ # You can use this method when you have an HTTP response that never changes,
270
+ # and the browser and proxies should cache it indefinitely.
271
+ #
272
+ # * +public+: By default, HTTP responses are private, cached only on the
273
+ # user's web browser. To allow proxies to cache the response, set +true+ to
274
+ # indicate that they can serve the cached response to all users.
275
+ def http_cache_forever(public: false)
276
+ expires_in 100.years, public: public
277
+
278
+ yield if stale?(etag: request.fullpath,
279
+ last_modified: Time.new(2011, 1, 1).utc,
280
+ public: public)
192
281
  end
193
282
 
194
283
  private
195
- def combine_etags(options)
196
- etags = etaggers.map { |etagger| instance_exec(options, &etagger) }.compact
197
- etags.unshift options[:etag]
284
+ def combine_etags(validator, options)
285
+ [validator, *etaggers.map { |etagger| instance_exec(options, &etagger) }].compact
198
286
  end
199
287
  end
200
288
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionController #:nodoc:
4
+ module ContentSecurityPolicy
5
+ # TODO: Documentation
6
+ extend ActiveSupport::Concern
7
+
8
+ include AbstractController::Helpers
9
+ include AbstractController::Callbacks
10
+
11
+ included do
12
+ helper_method :content_security_policy?
13
+ helper_method :content_security_policy_nonce
14
+ end
15
+
16
+ module ClassMethods
17
+ def content_security_policy(enabled = true, **options, &block)
18
+ before_action(options) do
19
+ if block_given?
20
+ policy = current_content_security_policy
21
+ yield policy
22
+ request.content_security_policy = policy
23
+ end
24
+
25
+ unless enabled
26
+ request.content_security_policy = nil
27
+ end
28
+ end
29
+ end
30
+
31
+ def content_security_policy_report_only(report_only = true, **options)
32
+ before_action(options) do
33
+ request.content_security_policy_report_only = report_only
34
+ end
35
+ end
36
+ end
37
+
38
+ private
39
+ def content_security_policy?
40
+ request.content_security_policy
41
+ end
42
+
43
+ def content_security_policy_nonce
44
+ request.content_security_policy_nonce
45
+ end
46
+
47
+ def current_content_security_policy
48
+ request.content_security_policy&.clone || ActionDispatch::ContentSecurityPolicy.new
49
+ end
50
+ end
51
+ end
@@ -1,15 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionController #:nodoc:
2
4
  module Cookies
3
5
  extend ActiveSupport::Concern
4
6
 
5
- include RackDelegation
6
-
7
7
  included do
8
- helper_method :cookies
8
+ helper_method :cookies if defined?(helper_method)
9
9
  end
10
10
 
11
11
  private
12
- def cookies
12
+ # The cookies for the current request. See ActionDispatch::Cookies for
13
+ # more information.
14
+ def cookies # :doc:
13
15
  request.cookie_jar
14
16
  end
15
17
  end
@@ -1,4 +1,7 @@
1
- require 'action_controller/metal/exceptions'
1
+ # frozen_string_literal: true
2
+
3
+ require "action_controller/metal/exceptions"
4
+ require "action_dispatch/http/content_disposition"
2
5
 
3
6
  module ActionController #:nodoc:
4
7
  # Methods for sending arbitrary data and for streaming files to the browser,
@@ -8,10 +11,10 @@ module ActionController #:nodoc:
8
11
 
9
12
  include ActionController::Rendering
10
13
 
11
- DEFAULT_SEND_FILE_TYPE = 'application/octet-stream'.freeze #:nodoc:
12
- DEFAULT_SEND_FILE_DISPOSITION = 'attachment'.freeze #:nodoc:
14
+ DEFAULT_SEND_FILE_TYPE = "application/octet-stream" #:nodoc:
15
+ DEFAULT_SEND_FILE_DISPOSITION = "attachment" #:nodoc:
13
16
 
14
- protected
17
+ private
15
18
  # Sends the file. This uses a server-appropriate method (such as X-Sendfile)
16
19
  # via the Rack::Sendfile middleware. The header to use is set via
17
20
  # +config.action_dispatch.x_sendfile_header+.
@@ -25,14 +28,13 @@ module ActionController #:nodoc:
25
28
  # * <tt>:filename</tt> - suggests a filename for the browser to use.
26
29
  # Defaults to <tt>File.basename(path)</tt>.
27
30
  # * <tt>:type</tt> - specifies an HTTP content type.
28
- # You can specify either a string or a symbol for a registered type register with
29
- # <tt>Mime::Type.register</tt>, for example :json
30
- # If omitted, type will be guessed from the file extension specified in <tt>:filename</tt>.
31
- # If no content type is registered for the extension, default type 'application/octet-stream' will be used.
31
+ # You can specify either a string or a symbol for a registered type with <tt>Mime::Type.register</tt>, for example :json.
32
+ # If omitted, the type will be inferred from the file extension specified in <tt>:filename</tt>.
33
+ # If no content type is registered for the extension, the default type 'application/octet-stream' will be used.
32
34
  # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
33
35
  # Valid values are 'inline' and 'attachment' (default).
34
36
  # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to 200.
35
- # * <tt>:url_based_filename</tt> - set to +true+ if you want the browser guess the filename from
37
+ # * <tt>:url_based_filename</tt> - set to +true+ if you want the browser to guess the filename from
36
38
  # the URL, which is necessary for i18n filenames on certain browsers
37
39
  # (setting <tt>:filename</tt> overrides this option).
38
40
  #
@@ -51,62 +53,42 @@ module ActionController #:nodoc:
51
53
  #
52
54
  # Show a 404 page in the browser:
53
55
  #
54
- # send_file '/path/to/404.html', type: 'text/html; charset=utf-8', status: 404
56
+ # send_file '/path/to/404.html', type: 'text/html; charset=utf-8', disposition: 'inline', status: 404
55
57
  #
56
58
  # Read about the other Content-* HTTP headers if you'd like to
57
59
  # provide the user with more information (such as Content-Description) in
58
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11.
60
+ # https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11.
59
61
  #
60
62
  # Also be aware that the document may be cached by proxies and browsers.
61
63
  # The Pragma and Cache-Control headers declare how the file may be cached
62
64
  # by intermediaries. They default to require clients to validate with
63
65
  # the server before releasing cached responses. See
64
- # http://www.mnot.net/cache_docs/ for an overview of web caching and
65
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
66
+ # https://www.mnot.net/cache_docs/ for an overview of web caching and
67
+ # https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
66
68
  # for the Cache-Control header spec.
67
69
  def send_file(path, options = {}) #:doc:
68
- raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path)
70
+ raise MissingFile, "Cannot read file #{path}" unless File.file?(path) && File.readable?(path)
69
71
 
70
72
  options[:filename] ||= File.basename(path) unless options[:url_based_filename]
71
73
  send_file_headers! options
72
74
 
73
75
  self.status = options[:status] || 200
74
76
  self.content_type = options[:content_type] if options.key?(:content_type)
75
- self.response_body = FileBody.new(path)
76
- end
77
-
78
- # Avoid having to pass an open file handle as the response body.
79
- # Rack::Sendfile will usually intercept the response and uses
80
- # the path directly, so there is no reason to open the file.
81
- class FileBody #:nodoc:
82
- attr_reader :to_path
83
-
84
- def initialize(path)
85
- @to_path = path
86
- end
87
-
88
- # Stream the file's contents if Rack::Sendfile isn't present.
89
- def each
90
- File.open(to_path, 'rb') do |file|
91
- while chunk = file.read(16384)
92
- yield chunk
93
- end
94
- end
95
- end
77
+ response.send_file path
96
78
  end
97
79
 
98
80
  # Sends the given binary data to the browser. This method is similar to
99
81
  # <tt>render plain: data</tt>, but also allows you to specify whether
100
82
  # the browser should display the response as a file attachment (i.e. in a
101
83
  # download dialog) or as inline data. You may also set the content type,
102
- # the apparent file name, and other things.
84
+ # the file name, and other things.
103
85
  #
104
86
  # Options:
105
87
  # * <tt>:filename</tt> - suggests a filename for the browser to use.
106
- # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify
107
- # either a string or a symbol for a registered type register with <tt>Mime::Type.register</tt>, for example :json
108
- # If omitted, type will be guessed from the file extension specified in <tt>:filename</tt>.
109
- # If no content type is registered for the extension, default type 'application/octet-stream' will be used.
88
+ # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'.
89
+ # You can specify either a string or a symbol for a registered type with <tt>Mime::Type.register</tt>, for example :json.
90
+ # If omitted, type will be inferred from the file extension specified in <tt>:filename</tt>.
91
+ # If no content type is registered for the extension, the default type 'application/octet-stream' will be used.
110
92
  # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
111
93
  # Valid values are 'inline' and 'attachment' (default).
112
94
  # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to 200.
@@ -126,14 +108,16 @@ module ActionController #:nodoc:
126
108
  # See +send_file+ for more information on HTTP Content-* headers and caching.
127
109
  def send_data(data, options = {}) #:doc:
128
110
  send_file_headers! options
129
- render options.slice(:status, :content_type).merge(:text => data)
111
+ render options.slice(:status, :content_type).merge(body: data)
130
112
  end
131
113
 
132
- private
133
114
  def send_file_headers!(options)
134
115
  type_provided = options.has_key?(:type)
135
116
 
136
117
  content_type = options.fetch(:type, DEFAULT_SEND_FILE_TYPE)
118
+ self.content_type = content_type
119
+ response.sending_file = true
120
+
137
121
  raise ArgumentError, ":type option required" if content_type.nil?
138
122
 
139
123
  if content_type.is_a?(Symbol)
@@ -143,21 +127,17 @@ module ActionController #:nodoc:
143
127
  else
144
128
  if !type_provided && options[:filename]
145
129
  # If type wasn't provided, try guessing from file extension.
146
- content_type = Mime::Type.lookup_by_extension(File.extname(options[:filename]).downcase.delete('.')) || content_type
130
+ content_type = Mime::Type.lookup_by_extension(File.extname(options[:filename]).downcase.delete(".")) || content_type
147
131
  end
148
132
  self.content_type = content_type
149
133
  end
150
134
 
151
135
  disposition = options.fetch(:disposition, DEFAULT_SEND_FILE_DISPOSITION)
152
- unless disposition.nil?
153
- disposition = disposition.to_s
154
- disposition += %(; filename="#{options[:filename]}") if options[:filename]
155
- headers['Content-Disposition'] = disposition
136
+ if disposition
137
+ headers["Content-Disposition"] = ActionDispatch::Http::ContentDisposition.format(disposition: disposition, filename: options[:filename])
156
138
  end
157
139
 
158
- headers['Content-Transfer-Encoding'] = 'binary'
159
-
160
- response.sending_file = true
140
+ headers["Content-Transfer-Encoding"] = "binary"
161
141
 
162
142
  # Fix a problem with IE 6.0 on opening downloaded files:
163
143
  # If Cache-Control: no-cache is set (which Rails does by default),
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionController
4
+ # Allows configuring default headers that will be automatically merged into
5
+ # each response.
6
+ module DefaultHeaders
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+ def make_response!(request)
11
+ ActionDispatch::Response.create.tap do |res|
12
+ res.request = request
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionController
4
+ # When you're using the flash, it's generally used as a conditional on the view.
5
+ # This means the content of the view depends on the flash. Which in turn means
6
+ # that the ETag for a response should be computed with the content of the flash
7
+ # in mind. This does that by including the content of the flash as a component
8
+ # in the ETag that's generated for a response.
9
+ module EtagWithFlash
10
+ extend ActiveSupport::Concern
11
+
12
+ include ActionController::ConditionalGet
13
+
14
+ included do
15
+ etag { flash unless flash.empty? }
16
+ end
17
+ end
18
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionController
2
4
  # When our views change, they should bubble up into HTTP cache freshness
3
5
  # and bust browser caches. So the template digest for the current action
@@ -22,29 +24,32 @@ module ActionController
22
24
  include ActionController::ConditionalGet
23
25
 
24
26
  included do
25
- class_attribute :etag_with_template_digest
26
- self.etag_with_template_digest = true
27
+ class_attribute :etag_with_template_digest, default: true
27
28
 
28
- ActiveSupport.on_load :action_view, yield: true do |action_view_base|
29
- etag do |options|
30
- determine_template_etag(options) if etag_with_template_digest
31
- end
29
+ etag do |options|
30
+ determine_template_etag(options) if etag_with_template_digest
32
31
  end
33
32
  end
34
33
 
35
34
  private
36
- def determine_template_etag(options)
37
- if template = pick_template_for_etag(options)
38
- lookup_and_digest_template(template)
35
+ def determine_template_etag(options)
36
+ if template = pick_template_for_etag(options)
37
+ lookup_and_digest_template(template)
38
+ end
39
39
  end
40
- end
41
40
 
42
- def pick_template_for_etag(options)
43
- options.fetch(:template) { "#{controller_name}/#{action_name}" }
44
- end
41
+ # Pick the template digest to include in the ETag. If the +:template+ option
42
+ # is present, use the named template. If +:template+ is +nil+ or absent, use
43
+ # the default controller/action template. If +:template+ is false, omit the
44
+ # template digest from the ETag.
45
+ def pick_template_for_etag(options)
46
+ unless options[:template] == false
47
+ options[:template] || "#{controller_path}/#{action_name}"
48
+ end
49
+ end
45
50
 
46
- def lookup_and_digest_template(template)
47
- ActionView::Digestor.digest name: template, finder: lookup_context
48
- end
51
+ def lookup_and_digest_template(template)
52
+ ActionView::Digestor.digest name: template, format: nil, finder: lookup_context
53
+ end
49
54
  end
50
55
  end