actionpack 6.1.7.5 → 7.1.3.1

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.

Potentially problematic release.


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

Files changed (160) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +355 -435
  3. data/MIT-LICENSE +2 -1
  4. data/README.rdoc +6 -7
  5. data/lib/abstract_controller/asset_paths.rb +1 -1
  6. data/lib/abstract_controller/base.rb +33 -37
  7. data/lib/abstract_controller/caching/fragments.rb +4 -2
  8. data/lib/abstract_controller/caching.rb +1 -1
  9. data/lib/abstract_controller/callbacks.rb +50 -11
  10. data/lib/abstract_controller/collector.rb +2 -2
  11. data/lib/abstract_controller/deprecator.rb +7 -0
  12. data/lib/abstract_controller/error.rb +1 -1
  13. data/lib/abstract_controller/helpers.rb +78 -30
  14. data/lib/abstract_controller/logger.rb +1 -1
  15. data/lib/abstract_controller/railties/routes_helpers.rb +3 -16
  16. data/lib/abstract_controller/rendering.rb +12 -14
  17. data/lib/abstract_controller/translation.rb +26 -7
  18. data/lib/abstract_controller/url_for.rb +6 -6
  19. data/lib/abstract_controller.rb +6 -0
  20. data/lib/action_controller/api.rb +12 -10
  21. data/lib/action_controller/base.rb +8 -21
  22. data/lib/action_controller/caching.rb +2 -0
  23. data/lib/action_controller/deprecator.rb +7 -0
  24. data/lib/action_controller/form_builder.rb +4 -2
  25. data/lib/action_controller/log_subscriber.rb +20 -7
  26. data/lib/action_controller/metal/basic_implicit_render.rb +3 -1
  27. data/lib/action_controller/metal/conditional_get.rb +137 -102
  28. data/lib/action_controller/metal/content_security_policy.rb +37 -3
  29. data/lib/action_controller/metal/cookies.rb +1 -1
  30. data/lib/action_controller/metal/data_streaming.rb +25 -31
  31. data/lib/action_controller/metal/default_headers.rb +2 -0
  32. data/lib/action_controller/metal/etag_with_flash.rb +3 -1
  33. data/lib/action_controller/metal/etag_with_template_digest.rb +2 -0
  34. data/lib/action_controller/metal/exceptions.rb +27 -30
  35. data/lib/action_controller/metal/flash.rb +6 -2
  36. data/lib/action_controller/metal/head.rb +9 -7
  37. data/lib/action_controller/metal/helpers.rb +5 -16
  38. data/lib/action_controller/metal/http_authentication.rb +78 -42
  39. data/lib/action_controller/metal/implicit_render.rb +5 -3
  40. data/lib/action_controller/metal/instrumentation.rb +62 -50
  41. data/lib/action_controller/metal/live.rb +67 -2
  42. data/lib/action_controller/metal/mime_responds.rb +5 -5
  43. data/lib/action_controller/metal/params_wrapper.rb +24 -13
  44. data/lib/action_controller/metal/permissions_policy.rb +20 -29
  45. data/lib/action_controller/metal/redirecting.rb +96 -23
  46. data/lib/action_controller/metal/renderers.rb +14 -15
  47. data/lib/action_controller/metal/rendering.rb +121 -16
  48. data/lib/action_controller/metal/request_forgery_protection.rb +208 -68
  49. data/lib/action_controller/metal/rescue.rb +7 -4
  50. data/lib/action_controller/metal/streaming.rb +74 -36
  51. data/lib/action_controller/metal/strong_parameters.rb +254 -151
  52. data/lib/action_controller/metal/testing.rb +9 -2
  53. data/lib/action_controller/metal/url_for.rb +10 -5
  54. data/lib/action_controller/metal.rb +89 -34
  55. data/lib/action_controller/railtie.rb +66 -9
  56. data/lib/action_controller/renderer.rb +99 -85
  57. data/lib/action_controller/test_case.rb +42 -11
  58. data/lib/action_controller.rb +10 -6
  59. data/lib/action_dispatch/constants.rb +32 -0
  60. data/lib/action_dispatch/deprecator.rb +7 -0
  61. data/lib/action_dispatch/http/cache.rb +21 -16
  62. data/lib/action_dispatch/http/content_security_policy.rb +122 -44
  63. data/lib/action_dispatch/http/filter_parameters.rb +14 -23
  64. data/lib/action_dispatch/http/headers.rb +3 -1
  65. data/lib/action_dispatch/http/mime_negotiation.rb +25 -15
  66. data/lib/action_dispatch/http/mime_type.rb +43 -22
  67. data/lib/action_dispatch/http/mime_types.rb +3 -1
  68. data/lib/action_dispatch/http/parameters.rb +6 -6
  69. data/lib/action_dispatch/http/permissions_policy.rb +57 -19
  70. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  71. data/lib/action_dispatch/http/request.rb +75 -51
  72. data/lib/action_dispatch/http/response.rb +81 -77
  73. data/lib/action_dispatch/http/upload.rb +15 -2
  74. data/lib/action_dispatch/http/url.rb +11 -19
  75. data/lib/action_dispatch/journey/formatter.rb +8 -2
  76. data/lib/action_dispatch/journey/gtg/builder.rb +11 -12
  77. data/lib/action_dispatch/journey/gtg/simulator.rb +10 -4
  78. data/lib/action_dispatch/journey/gtg/transition_table.rb +77 -21
  79. data/lib/action_dispatch/journey/nodes/node.rb +70 -5
  80. data/lib/action_dispatch/journey/path/pattern.rb +36 -27
  81. data/lib/action_dispatch/journey/route.rb +8 -14
  82. data/lib/action_dispatch/journey/router/utils.rb +2 -2
  83. data/lib/action_dispatch/journey/router.rb +10 -9
  84. data/lib/action_dispatch/journey/routes.rb +5 -5
  85. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  86. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  87. data/lib/action_dispatch/log_subscriber.rb +23 -0
  88. data/lib/action_dispatch/middleware/actionable_exceptions.rb +5 -7
  89. data/lib/action_dispatch/middleware/assume_ssl.rb +24 -0
  90. data/lib/action_dispatch/middleware/callbacks.rb +2 -0
  91. data/lib/action_dispatch/middleware/cookies.rb +97 -107
  92. data/lib/action_dispatch/middleware/debug_exceptions.rb +31 -28
  93. data/lib/action_dispatch/middleware/debug_locks.rb +7 -4
  94. data/lib/action_dispatch/middleware/debug_view.rb +7 -2
  95. data/lib/action_dispatch/middleware/exception_wrapper.rb +190 -27
  96. data/lib/action_dispatch/middleware/executor.rb +3 -0
  97. data/lib/action_dispatch/middleware/flash.rb +24 -18
  98. data/lib/action_dispatch/middleware/host_authorization.rb +19 -20
  99. data/lib/action_dispatch/middleware/public_exceptions.rb +5 -3
  100. data/lib/action_dispatch/middleware/reloader.rb +7 -5
  101. data/lib/action_dispatch/middleware/remote_ip.rb +32 -19
  102. data/lib/action_dispatch/middleware/request_id.rb +5 -3
  103. data/lib/action_dispatch/middleware/server_timing.rb +76 -0
  104. data/lib/action_dispatch/middleware/session/abstract_store.rb +6 -1
  105. data/lib/action_dispatch/middleware/session/cache_store.rb +2 -0
  106. data/lib/action_dispatch/middleware/session/cookie_store.rb +19 -13
  107. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +3 -1
  108. data/lib/action_dispatch/middleware/show_exceptions.rb +30 -25
  109. data/lib/action_dispatch/middleware/ssl.rb +18 -6
  110. data/lib/action_dispatch/middleware/stack.rb +34 -11
  111. data/lib/action_dispatch/middleware/static.rb +16 -16
  112. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
  113. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +5 -5
  114. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +4 -11
  115. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
  116. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +2 -2
  117. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +10 -5
  118. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +7 -3
  119. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +9 -9
  120. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
  121. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +3 -3
  122. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +45 -18
  123. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -15
  124. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +4 -4
  125. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +6 -6
  126. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +7 -7
  127. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
  128. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  129. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +3 -0
  130. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +64 -55
  131. data/lib/action_dispatch/railtie.rb +20 -4
  132. data/lib/action_dispatch/request/session.rb +59 -19
  133. data/lib/action_dispatch/request/utils.rb +8 -3
  134. data/lib/action_dispatch/routing/inspector.rb +55 -7
  135. data/lib/action_dispatch/routing/mapper.rb +117 -107
  136. data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -0
  137. data/lib/action_dispatch/routing/redirection.rb +20 -8
  138. data/lib/action_dispatch/routing/route_set.rb +67 -27
  139. data/lib/action_dispatch/routing/routes_proxy.rb +11 -16
  140. data/lib/action_dispatch/routing/url_for.rb +29 -26
  141. data/lib/action_dispatch/routing.rb +12 -13
  142. data/lib/action_dispatch/system_test_case.rb +8 -8
  143. data/lib/action_dispatch/system_testing/browser.rb +20 -29
  144. data/lib/action_dispatch/system_testing/driver.rb +34 -18
  145. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +35 -20
  146. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +0 -8
  147. data/lib/action_dispatch/testing/assertion_response.rb +1 -1
  148. data/lib/action_dispatch/testing/assertions/response.rb +14 -7
  149. data/lib/action_dispatch/testing/assertions/routing.rb +70 -30
  150. data/lib/action_dispatch/testing/assertions.rb +3 -4
  151. data/lib/action_dispatch/testing/integration.rb +33 -25
  152. data/lib/action_dispatch/testing/request_encoder.rb +4 -1
  153. data/lib/action_dispatch/testing/test_process.rb +5 -30
  154. data/lib/action_dispatch/testing/test_request.rb +1 -1
  155. data/lib/action_dispatch/testing/test_response.rb +34 -2
  156. data/lib/action_dispatch.rb +38 -4
  157. data/lib/action_pack/gem_version.rb +4 -4
  158. data/lib/action_pack/version.rb +1 -1
  159. data/lib/action_pack.rb +1 -1
  160. metadata +67 -30
@@ -31,7 +31,7 @@ module ActionController
31
31
 
32
32
  # ActionController::TestCase will be deprecated and moved to a gem in the future.
33
33
  # Please use ActionDispatch::IntegrationTest going forward.
34
- class TestRequest < ActionDispatch::TestRequest #:nodoc:
34
+ class TestRequest < ActionDispatch::TestRequest # :nodoc:
35
35
  DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup
36
36
  DEFAULT_ENV.delete "PATH_INFO"
37
37
 
@@ -127,6 +127,9 @@ module ActionController
127
127
  fetch_header("PATH_INFO") do |k|
128
128
  set_header k, generated_path
129
129
  end
130
+ fetch_header("ORIGINAL_FULLPATH") do |k|
131
+ set_header k, fullpath
132
+ end
130
133
  path_parameters[:controller] = controller_path
131
134
  path_parameters[:action] = action
132
135
 
@@ -179,14 +182,15 @@ module ActionController
179
182
 
180
183
  # Methods #destroy and #load! are overridden to avoid calling methods on the
181
184
  # @store object, which does not exist for the TestSession class.
182
- class TestSession < Rack::Session::Abstract::PersistedSecure::SecureSessionHash #:nodoc:
185
+ class TestSession < Rack::Session::Abstract::PersistedSecure::SecureSessionHash # :nodoc:
183
186
  DEFAULT_OPTIONS = Rack::Session::Abstract::Persisted::DEFAULT_OPTIONS
184
187
 
185
- def initialize(session = {})
188
+ def initialize(session = {}, id = Rack::Session::SessionId.new(SecureRandom.hex(16)))
186
189
  super(nil, nil)
187
- @id = Rack::Session::SessionId.new(SecureRandom.hex(16))
190
+ @id = id
188
191
  @data = stringify_keys(session)
189
192
  @loaded = true
193
+ @initially_empty = @data.empty?
190
194
  end
191
195
 
192
196
  def exists?
@@ -214,21 +218,31 @@ module ActionController
214
218
  @data.fetch(key.to_s, *args, &block)
215
219
  end
216
220
 
221
+ def enabled?
222
+ true
223
+ end
224
+
225
+ def id_was
226
+ @id
227
+ end
228
+
217
229
  private
218
230
  def load!
219
231
  @id
220
232
  end
221
233
  end
222
234
 
235
+ # = Action Controller Test Case
236
+ #
223
237
  # Superclass for ActionController functional tests. Functional tests allow you to
224
238
  # test a single controller action per test method.
225
239
  #
226
240
  # == Use integration style controller tests over functional style controller tests.
227
241
  #
228
- # Rails discourages the use of functional tests in favor of integration tests
242
+ # \Rails discourages the use of functional tests in favor of integration tests
229
243
  # (use ActionDispatch::IntegrationTest).
230
244
  #
231
- # New Rails applications no longer generate functional style controller tests and they should
245
+ # New \Rails applications no longer generate functional style controller tests and they should
232
246
  # only be used for backward compatibility. Integration style controller tests perform actual
233
247
  # requests, whereas functional style controller tests merely simulate a request. Besides,
234
248
  # integration tests are as fast as functional tests and provide lot of helpers such as +as+,
@@ -237,7 +251,7 @@ module ActionController
237
251
  # == Basic example
238
252
  #
239
253
  # Functional tests are written as follows:
240
- # 1. First, one uses the +get+, +post+, +patch+, +put+, +delete+ or +head+ method to simulate
254
+ # 1. First, one uses the +get+, +post+, +patch+, +put+, +delete+, or +head+ method to simulate
241
255
  # an HTTP request.
242
256
  # 2. Then, one asserts whether the current state is as expected. "State" can be anything:
243
257
  # the controller's HTTP response, the database contents, etc.
@@ -329,6 +343,8 @@ module ActionController
329
343
  #
330
344
  # assert_redirected_to page_url(title: 'foo')
331
345
  class TestCase < ActiveSupport::TestCase
346
+ singleton_class.attr_accessor :executor_around_each_request
347
+
332
348
  module Behavior
333
349
  extend ActiveSupport::Concern
334
350
  include ActionDispatch::TestProcess
@@ -385,7 +401,7 @@ module ActionController
385
401
  #
386
402
  # You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with
387
403
  # +post+, +patch+, +put+, +delete+, and +head+.
388
- # Example sending parameters, session and setting a flash message:
404
+ # Example sending parameters, session, and setting a flash message:
389
405
  #
390
406
  # get :show,
391
407
  # params: { id: 7 },
@@ -455,13 +471,19 @@ module ActionController
455
471
  # session: { user_id: 1 },
456
472
  # flash: { notice: 'This is flash message' }
457
473
  #
458
- # To simulate +GET+, +POST+, +PATCH+, +PUT+, +DELETE+ and +HEAD+ requests
474
+ # To simulate +GET+, +POST+, +PATCH+, +PUT+, +DELETE+, and +HEAD+ requests
459
475
  # prefer using #get, #post, #patch, #put, #delete and #head methods
460
476
  # respectively which will make tests more expressive.
461
477
  #
478
+ # It's not recommended to make more than one request in the same test. Instance
479
+ # variables that are set in one request will not persist to the next request,
480
+ # but it's not guaranteed that all \Rails internal state will be reset. Prefer
481
+ # ActionDispatch::IntegrationTest for making multiple requests in the same test.
482
+ #
462
483
  # Note that the request method is not verified.
463
484
  def process(action, method: "GET", params: nil, session: nil, body: nil, flash: {}, format: nil, xhr: false, as: nil)
464
485
  check_required_ivars
486
+ @controller.clear_instance_variables_between_requests
465
487
 
466
488
  action = +action.to_s
467
489
  http_method = method.to_s.upcase
@@ -574,10 +596,19 @@ module ActionController
574
596
  end
575
597
  end
576
598
 
599
+ def wrap_execution(&block)
600
+ if ActionController::TestCase.executor_around_each_request && defined?(Rails.application) && Rails.application
601
+ Rails.application.executor.wrap(&block)
602
+ else
603
+ yield
604
+ end
605
+ end
606
+
577
607
  def process_controller_response(action, cookies, xhr)
578
608
  begin
579
609
  @controller.recycle!
580
- @controller.dispatch(action, @request, @response)
610
+
611
+ wrap_execution { @controller.dispatch(action, @request, @response) }
581
612
  ensure
582
613
  @request = @controller.request
583
614
  @response = @controller.response
@@ -623,7 +654,7 @@ module ActionController
623
654
  end
624
655
 
625
656
  def check_required_ivars
626
- # Sanity check for required instance variables so we can give an
657
+ # Check for required instance variables so we can give an
627
658
  # understandable error message.
628
659
  [:@routes, :@controller, :@request, :@response].each do |iv_name|
629
660
  if !instance_variable_defined?(iv_name) || instance_variable_get(iv_name).nil?
@@ -2,8 +2,17 @@
2
2
 
3
3
  require "abstract_controller"
4
4
  require "action_dispatch"
5
+ require "action_controller/deprecator"
5
6
  require "action_controller/metal/strong_parameters"
7
+ require "action_controller/metal/exceptions"
6
8
 
9
+ # = Action Controller
10
+ #
11
+ # Action Controller is a module of Action Pack.
12
+ #
13
+ # Action Controller provides a base controller class that can be subclassed to
14
+ # implement filters and actions to handle requests. The result of an action is
15
+ # typically content generated from views.
7
16
  module ActionController
8
17
  extend ActiveSupport::Autoload
9
18
 
@@ -18,10 +27,6 @@ module ActionController
18
27
  end
19
28
 
20
29
  autoload_under "metal" do
21
- eager_autoload do
22
- autoload :Live
23
- end
24
-
25
30
  autoload :ConditionalGet
26
31
  autoload :ContentSecurityPolicy
27
32
  autoload :Cookies
@@ -37,6 +42,7 @@ module ActionController
37
42
  autoload :BasicImplicitRender
38
43
  autoload :ImplicitRender
39
44
  autoload :Instrumentation
45
+ autoload :Live
40
46
  autoload :Logging
41
47
  autoload :MimeResponds
42
48
  autoload :ParamsWrapper
@@ -62,8 +68,6 @@ end
62
68
 
63
69
  # Common Active Support usage in Action Controller
64
70
  require "active_support/core_ext/module/attribute_accessors"
65
- require "active_support/core_ext/load_error"
66
71
  require "active_support/core_ext/module/attr_internal"
67
72
  require "active_support/core_ext/name_error"
68
- require "active_support/core_ext/uri"
69
73
  require "active_support/inflector"
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rack/version"
4
+
5
+ module ActionDispatch
6
+ module Constants
7
+ # Response Header keys for Rack 2.x and 3.x
8
+ if Gem::Version.new(Rack::RELEASE) < Gem::Version.new("3")
9
+ VARY = "Vary"
10
+ CONTENT_ENCODING = "Content-Encoding"
11
+ CONTENT_SECURITY_POLICY = "Content-Security-Policy"
12
+ CONTENT_SECURITY_POLICY_REPORT_ONLY = "Content-Security-Policy-Report-Only"
13
+ LOCATION = "Location"
14
+ FEATURE_POLICY = "Feature-Policy"
15
+ X_REQUEST_ID = "X-Request-Id"
16
+ X_CASCADE = "X-Cascade"
17
+ SERVER_TIMING = "Server-Timing"
18
+ STRICT_TRANSPORT_SECURITY = "Strict-Transport-Security"
19
+ else
20
+ VARY = "vary"
21
+ CONTENT_ENCODING = "content-encoding"
22
+ CONTENT_SECURITY_POLICY = "content-security-policy"
23
+ CONTENT_SECURITY_POLICY_REPORT_ONLY = "content-security-policy-report-only"
24
+ LOCATION = "location"
25
+ FEATURE_POLICY = "feature-policy"
26
+ X_REQUEST_ID = "x-request-id"
27
+ X_CASCADE = "x-cascade"
28
+ SERVER_TIMING = "server-timing"
29
+ STRICT_TRANSPORT_SECURITY = "strict-transport-security"
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionDispatch
4
+ def self.deprecator # :nodoc:
5
+ @deprecator ||= ActiveSupport::Deprecation.new
6
+ end
7
+ end
@@ -32,8 +32,8 @@ module ActionDispatch
32
32
  end
33
33
  end
34
34
 
35
- # Check response freshness (Last-Modified and ETag) against request
36
- # If-Modified-Since and If-None-Match conditions. If both headers are
35
+ # Check response freshness (+Last-Modified+ and ETag) against request
36
+ # +If-Modified-Since+ and +If-None-Match+ conditions. If both headers are
37
37
  # supplied, both must match, or the request is not considered fresh.
38
38
  def fresh?(response)
39
39
  last_modified = if_modified_since
@@ -81,8 +81,8 @@ module ActionDispatch
81
81
 
82
82
  # This method sets a weak ETag validator on the response so browsers
83
83
  # and proxies may cache the response, keyed on the ETag. On subsequent
84
- # requests, the If-None-Match header is set to the cached ETag. If it
85
- # matches the current ETag, we can return a 304 Not Modified response
84
+ # requests, the +If-None-Match+ header is set to the cached ETag. If it
85
+ # matches the current ETag, we can return a <tt>304 Not Modified</tt> response
86
86
  # with no body, letting the browser or proxy know that their cache is
87
87
  # current. Big savings in request time and network bandwidth.
88
88
  #
@@ -92,7 +92,7 @@ module ActionDispatch
92
92
  # is viewing.
93
93
  #
94
94
  # Strong ETags are considered byte-for-byte identical. They allow a
95
- # browser or proxy cache to support Range requests, useful for paging
95
+ # browser or proxy cache to support +Range+ requests, useful for paging
96
96
  # through a PDF file or scrubbing through a video. Some CDNs only
97
97
  # support strong ETags and will ignore weak ETags entirely.
98
98
  #
@@ -112,12 +112,12 @@ module ActionDispatch
112
112
 
113
113
  def etag?; etag; end
114
114
 
115
- # True if an ETag is set and it's a weak validator (preceded with W/)
115
+ # True if an ETag is set, and it's a weak validator (preceded with <tt>W/</tt>).
116
116
  def weak_etag?
117
117
  etag? && etag.start_with?('W/"')
118
118
  end
119
119
 
120
- # True if an ETag is set and it isn't a weak validator (not preceded with W/)
120
+ # True if an ETag is set, and it isn't a weak validator (not preceded with <tt>W/</tt>).
121
121
  def strong_etag?
122
122
  etag? && !weak_etag?
123
123
  end
@@ -138,15 +138,13 @@ module ActionDispatch
138
138
  def cache_control_segments
139
139
  if cache_control = _cache_control
140
140
  cache_control.delete(" ").split(",")
141
- else
142
- []
143
141
  end
144
142
  end
145
143
 
146
144
  def cache_control_headers
147
145
  cache_control = {}
148
146
 
149
- cache_control_segments.each do |segment|
147
+ cache_control_segments&.each do |segment|
150
148
  directive, argument = segment.split("=", 2)
151
149
 
152
150
  if SPECIAL_KEYS.include? directive
@@ -187,13 +185,20 @@ module ActionDispatch
187
185
 
188
186
  return if control.empty? && cache_control.empty? # Let middleware handle default behavior
189
187
 
190
- if extras = control.delete(:extras)
191
- cache_control[:extras] ||= []
192
- cache_control[:extras] += extras
193
- cache_control[:extras].uniq!
194
- end
188
+ if cache_control.any?
189
+ # Any caching directive coming from a controller overrides
190
+ # no-cache/no-store in the default Cache-Control header.
191
+ control.delete(:no_cache)
192
+ control.delete(:no_store)
193
+
194
+ if extras = control.delete(:extras)
195
+ cache_control[:extras] ||= []
196
+ cache_control[:extras] += extras
197
+ cache_control[:extras].uniq!
198
+ end
195
199
 
196
- control.merge! cache_control
200
+ control.merge! cache_control
201
+ end
197
202
 
198
203
  options = []
199
204
 
@@ -3,23 +3,43 @@
3
3
  require "active_support/core_ext/object/deep_dup"
4
4
  require "active_support/core_ext/array/wrap"
5
5
 
6
- module ActionDispatch #:nodoc:
6
+ module ActionDispatch # :nodoc:
7
+ # = Action Dispatch Content Security Policy
8
+ #
9
+ # Configures the HTTP
10
+ # {Content-Security-Policy}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy]
11
+ # response header to help protect against XSS and injection attacks.
12
+ #
13
+ # Example global policy:
14
+ #
15
+ # Rails.application.config.content_security_policy do |policy|
16
+ # policy.default_src :self, :https
17
+ # policy.font_src :self, :https, :data
18
+ # policy.img_src :self, :https, :data
19
+ # policy.object_src :none
20
+ # policy.script_src :self, :https
21
+ # policy.style_src :self, :https
22
+ #
23
+ # # Specify URI for violation reports
24
+ # policy.report_uri "/csp-violation-report-endpoint"
25
+ # end
7
26
  class ContentSecurityPolicy
8
27
  class Middleware
9
- CONTENT_TYPE = "Content-Type"
10
- POLICY = "Content-Security-Policy"
11
- POLICY_REPORT_ONLY = "Content-Security-Policy-Report-Only"
12
-
13
28
  def initialize(app)
14
29
  @app = app
15
30
  end
16
31
 
17
32
  def call(env)
18
- request = ActionDispatch::Request.new env
19
- _, headers, _ = response = @app.call(env)
33
+ status, headers, _ = response = @app.call(env)
34
+
35
+ # Returning CSP headers with a 304 Not Modified is harmful, since nonces in the new
36
+ # CSP headers might not match nonces in the cached HTML.
37
+ return response if status == 304
20
38
 
21
39
  return response if policy_present?(headers)
22
40
 
41
+ request = ActionDispatch::Request.new env
42
+
23
43
  if policy = request.content_security_policy
24
44
  nonce = request.content_security_policy_nonce
25
45
  nonce_directives = request.content_security_policy_nonce_directives
@@ -33,14 +53,15 @@ module ActionDispatch #:nodoc:
33
53
  private
34
54
  def header_name(request)
35
55
  if request.content_security_policy_report_only
36
- POLICY_REPORT_ONLY
56
+ ActionDispatch::Constants::CONTENT_SECURITY_POLICY_REPORT_ONLY
37
57
  else
38
- POLICY
58
+ ActionDispatch::Constants::CONTENT_SECURITY_POLICY
39
59
  end
40
60
  end
41
61
 
42
62
  def policy_present?(headers)
43
- headers[POLICY] || headers[POLICY_REPORT_ONLY]
63
+ headers[ActionDispatch::Constants::CONTENT_SECURITY_POLICY] ||
64
+ headers[ActionDispatch::Constants::CONTENT_SECURITY_POLICY_REPORT_ONLY]
44
65
  end
45
66
  end
46
67
 
@@ -100,43 +121,48 @@ module ActionDispatch #:nodoc:
100
121
  end
101
122
 
102
123
  MAPPINGS = {
103
- self: "'self'",
104
- unsafe_eval: "'unsafe-eval'",
105
- unsafe_inline: "'unsafe-inline'",
106
- none: "'none'",
107
- http: "http:",
108
- https: "https:",
109
- data: "data:",
110
- mediastream: "mediastream:",
111
- blob: "blob:",
112
- filesystem: "filesystem:",
113
- report_sample: "'report-sample'",
114
- strict_dynamic: "'strict-dynamic'",
115
- ws: "ws:",
116
- wss: "wss:"
124
+ self: "'self'",
125
+ unsafe_eval: "'unsafe-eval'",
126
+ unsafe_hashes: "'unsafe-hashes'",
127
+ unsafe_inline: "'unsafe-inline'",
128
+ none: "'none'",
129
+ http: "http:",
130
+ https: "https:",
131
+ data: "data:",
132
+ mediastream: "mediastream:",
133
+ allow_duplicates: "'allow-duplicates'",
134
+ blob: "blob:",
135
+ filesystem: "filesystem:",
136
+ report_sample: "'report-sample'",
137
+ script: "'script'",
138
+ strict_dynamic: "'strict-dynamic'",
139
+ ws: "ws:",
140
+ wss: "wss:"
117
141
  }.freeze
118
142
 
119
143
  DIRECTIVES = {
120
- base_uri: "base-uri",
121
- child_src: "child-src",
122
- connect_src: "connect-src",
123
- default_src: "default-src",
124
- font_src: "font-src",
125
- form_action: "form-action",
126
- frame_ancestors: "frame-ancestors",
127
- frame_src: "frame-src",
128
- img_src: "img-src",
129
- manifest_src: "manifest-src",
130
- media_src: "media-src",
131
- object_src: "object-src",
132
- prefetch_src: "prefetch-src",
133
- script_src: "script-src",
134
- script_src_attr: "script-src-attr",
135
- script_src_elem: "script-src-elem",
136
- style_src: "style-src",
137
- style_src_attr: "style-src-attr",
138
- style_src_elem: "style-src-elem",
139
- worker_src: "worker-src"
144
+ base_uri: "base-uri",
145
+ child_src: "child-src",
146
+ connect_src: "connect-src",
147
+ default_src: "default-src",
148
+ font_src: "font-src",
149
+ form_action: "form-action",
150
+ frame_ancestors: "frame-ancestors",
151
+ frame_src: "frame-src",
152
+ img_src: "img-src",
153
+ manifest_src: "manifest-src",
154
+ media_src: "media-src",
155
+ object_src: "object-src",
156
+ prefetch_src: "prefetch-src",
157
+ require_trusted_types_for: "require-trusted-types-for",
158
+ script_src: "script-src",
159
+ script_src_attr: "script-src-attr",
160
+ script_src_elem: "script-src-elem",
161
+ style_src: "style-src",
162
+ style_src_attr: "style-src-attr",
163
+ style_src_elem: "style-src-elem",
164
+ trusted_types: "trusted-types",
165
+ worker_src: "worker-src"
140
166
  }.freeze
141
167
 
142
168
  DEFAULT_NONCE_DIRECTIVES = %w[script-src style-src].freeze
@@ -164,6 +190,15 @@ module ActionDispatch #:nodoc:
164
190
  end
165
191
  end
166
192
 
193
+ # Specify whether to prevent the user agent from loading any assets over
194
+ # HTTP when the page uses HTTPS:
195
+ #
196
+ # policy.block_all_mixed_content
197
+ #
198
+ # Pass +false+ to allow it again:
199
+ #
200
+ # policy.block_all_mixed_content false
201
+ #
167
202
  def block_all_mixed_content(enabled = true)
168
203
  if enabled
169
204
  @directives["block-all-mixed-content"] = true
@@ -172,6 +207,14 @@ module ActionDispatch #:nodoc:
172
207
  end
173
208
  end
174
209
 
210
+ # Restricts the set of plugins that can be embedded:
211
+ #
212
+ # policy.plugin_types "application/x-shockwave-flash"
213
+ #
214
+ # Leave empty to allow all plugins:
215
+ #
216
+ # policy.plugin_types
217
+ #
175
218
  def plugin_types(*types)
176
219
  if types.first
177
220
  @directives["plugin-types"] = types
@@ -180,10 +223,24 @@ module ActionDispatch #:nodoc:
180
223
  end
181
224
  end
182
225
 
226
+ # Enable the {report-uri}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-uri]
227
+ # directive. Violation reports will be sent to the specified URI:
228
+ #
229
+ # policy.report_uri "/csp-violation-report-endpoint"
230
+ #
183
231
  def report_uri(uri)
184
232
  @directives["report-uri"] = [uri]
185
233
  end
186
234
 
235
+ # Specify asset types for which {Subresource Integrity}[https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity]
236
+ # is required:
237
+ #
238
+ # policy.require_sri_for :script, :style
239
+ #
240
+ # Leave empty to not require Subresource Integrity:
241
+ #
242
+ # policy.require_sri_for
243
+ #
187
244
  def require_sri_for(*types)
188
245
  if types.first
189
246
  @directives["require-sri-for"] = types
@@ -192,6 +249,19 @@ module ActionDispatch #:nodoc:
192
249
  end
193
250
  end
194
251
 
252
+ # Specify whether a {sandbox}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/sandbox]
253
+ # should be enabled for the requested resource:
254
+ #
255
+ # policy.sandbox
256
+ #
257
+ # Values can be passed as arguments:
258
+ #
259
+ # policy.sandbox "allow-scripts", "allow-modals"
260
+ #
261
+ # Pass +false+ to disable the sandbox:
262
+ #
263
+ # policy.sandbox false
264
+ #
195
265
  def sandbox(*values)
196
266
  if values.empty?
197
267
  @directives["sandbox"] = true
@@ -202,6 +272,14 @@ module ActionDispatch #:nodoc:
202
272
  end
203
273
  end
204
274
 
275
+ # Specify whether user agents should treat any assets over HTTP as HTTPS:
276
+ #
277
+ # policy.upgrade_insecure_requests
278
+ #
279
+ # Pass +false+ to disable it:
280
+ #
281
+ # policy.upgrade_insecure_requests false
282
+ #
205
283
  def upgrade_insecure_requests(enabled = true)
206
284
  if enabled
207
285
  @directives["upgrade-insecure-requests"] = true
@@ -4,28 +4,15 @@ require "active_support/parameter_filter"
4
4
 
5
5
  module ActionDispatch
6
6
  module Http
7
- # Allows you to specify sensitive parameters which will be replaced from
8
- # the request log by looking in the query string of the request and all
9
- # sub-hashes of the params hash to filter. Filtering only certain sub-keys
10
- # from a hash is possible by using the dot notation: 'credit_card.number'.
11
- # If a block is given, each key and value of the params hash and all
12
- # sub-hashes are passed to it, where the value or the key can be replaced using
13
- # String#replace or similar methods.
7
+ # = Action Dispatch HTTP Filter Parameters
14
8
  #
15
- # env["action_dispatch.parameter_filter"] = [:password]
16
- # => replaces the value to all keys matching /password/i with "[FILTERED]"
9
+ # Allows you to specify sensitive query string and POST parameters to filter
10
+ # from the request log.
17
11
  #
12
+ # # Replaces values with "[FILTERED]" for keys that match /foo|bar/i.
18
13
  # env["action_dispatch.parameter_filter"] = [:foo, "bar"]
19
- # => replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
20
14
  #
21
- # env["action_dispatch.parameter_filter"] = [ "credit_card.code" ]
22
- # => replaces { credit_card: {code: "xxxx"} } with "[FILTERED]", does not
23
- # change { file: { code: "xxxx"} }
24
- #
25
- # env["action_dispatch.parameter_filter"] = -> (k, v) do
26
- # v.reverse! if k.match?(/secret/i)
27
- # end
28
- # => reverses the value to all keys matching /secret/i
15
+ # For more information about filter behavior, see ActiveSupport::ParameterFilter.
29
16
  module FilterParameters
30
17
  ENV_MATCH = [/RAW_POST_DATA/, "rack.request.form_vars"] # :nodoc:
31
18
  NULL_PARAM_FILTER = ActiveSupport::ParameterFilter.new # :nodoc:
@@ -36,6 +23,7 @@ module ActionDispatch
36
23
  @filtered_parameters = nil
37
24
  @filtered_env = nil
38
25
  @filtered_path = nil
26
+ @parameter_filter = nil
39
27
  end
40
28
 
41
29
  # Returns a hash of parameters with all sensitive data replaced.
@@ -55,13 +43,16 @@ module ActionDispatch
55
43
  @filtered_path ||= query_string.empty? ? path : "#{path}?#{filtered_query_string}"
56
44
  end
57
45
 
58
- private
59
- def parameter_filter # :doc:
60
- parameter_filter_for fetch_header("action_dispatch.parameter_filter") {
61
- return NULL_PARAM_FILTER
62
- }
46
+ # Returns the +ActiveSupport::ParameterFilter+ object used to filter in this request.
47
+ def parameter_filter
48
+ @parameter_filter ||= if has_header?("action_dispatch.parameter_filter")
49
+ parameter_filter_for get_header("action_dispatch.parameter_filter")
50
+ else
51
+ NULL_PARAM_FILTER
52
+ end
63
53
  end
64
54
 
55
+ private
65
56
  def env_filter # :doc:
66
57
  user_key = fetch_header("action_dispatch.parameter_filter") {
67
58
  return NULL_ENV_FILTER
@@ -2,6 +2,8 @@
2
2
 
3
3
  module ActionDispatch
4
4
  module Http
5
+ # = Action Dispatch HTTP \Headers
6
+ #
5
7
  # Provides access to the request's HTTP headers from the environment.
6
8
  #
7
9
  # env = { "CONTENT_TYPE" => "text/plain", "HTTP_USER_AGENT" => "curl/7.43.0" }
@@ -65,7 +67,7 @@ module ActionDispatch
65
67
  @req.set_header env_name(key), value
66
68
  end
67
69
 
68
- # Add a value to a multivalued header like Vary or Accept-Encoding.
70
+ # Add a value to a multivalued header like +Vary+ or +Accept-Encoding+.
69
71
  def add(key, value)
70
72
  @req.add_header env_name(key), value
71
73
  end