actionpack 5.2.1 → 7.0.2.4

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 (167) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +264 -220
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -6
  5. data/lib/abstract_controller/asset_paths.rb +1 -1
  6. data/lib/abstract_controller/base.rb +24 -4
  7. data/lib/abstract_controller/caching/fragments.rb +8 -24
  8. data/lib/abstract_controller/caching.rb +2 -2
  9. data/lib/abstract_controller/callbacks.rb +34 -8
  10. data/lib/abstract_controller/collector.rb +5 -4
  11. data/lib/abstract_controller/error.rb +1 -1
  12. data/lib/abstract_controller/helpers.rb +107 -90
  13. data/lib/abstract_controller/logger.rb +1 -1
  14. data/lib/abstract_controller/railties/routes_helpers.rb +19 -1
  15. data/lib/abstract_controller/rendering.rb +9 -9
  16. data/lib/abstract_controller/translation.rb +12 -5
  17. data/lib/abstract_controller/url_for.rb +4 -6
  18. data/lib/abstract_controller.rb +2 -0
  19. data/lib/action_controller/api.rb +5 -4
  20. data/lib/action_controller/base.rb +6 -9
  21. data/lib/action_controller/caching.rb +1 -3
  22. data/lib/action_controller/log_subscriber.rb +13 -9
  23. data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
  24. data/lib/action_controller/metal/conditional_get.rb +57 -6
  25. data/lib/action_controller/metal/content_security_policy.rb +2 -3
  26. data/lib/action_controller/metal/cookies.rb +4 -2
  27. data/lib/action_controller/metal/data_streaming.rb +9 -18
  28. data/lib/action_controller/metal/default_headers.rb +17 -0
  29. data/lib/action_controller/metal/etag_with_template_digest.rb +4 -6
  30. data/lib/action_controller/metal/exceptions.rb +55 -12
  31. data/lib/action_controller/metal/flash.rb +10 -6
  32. data/lib/action_controller/metal/head.rb +7 -4
  33. data/lib/action_controller/metal/helpers.rb +15 -6
  34. data/lib/action_controller/metal/http_authentication.rb +41 -39
  35. data/lib/action_controller/metal/implicit_render.rb +5 -15
  36. data/lib/action_controller/metal/instrumentation.rb +59 -55
  37. data/lib/action_controller/metal/live.rb +80 -33
  38. data/lib/action_controller/metal/logging.rb +20 -0
  39. data/lib/action_controller/metal/mime_responds.rb +22 -7
  40. data/lib/action_controller/metal/parameter_encoding.rb +35 -4
  41. data/lib/action_controller/metal/params_wrapper.rb +50 -31
  42. data/lib/action_controller/metal/permissions_policy.rb +46 -0
  43. data/lib/action_controller/metal/redirecting.rb +93 -23
  44. data/lib/action_controller/metal/renderers.rb +4 -4
  45. data/lib/action_controller/metal/rendering.rb +14 -9
  46. data/lib/action_controller/metal/request_forgery_protection.rb +160 -58
  47. data/lib/action_controller/metal/rescue.rb +2 -2
  48. data/lib/action_controller/metal/streaming.rb +1 -4
  49. data/lib/action_controller/metal/strong_parameters.rb +236 -88
  50. data/lib/action_controller/metal/testing.rb +9 -2
  51. data/lib/action_controller/metal/url_for.rb +1 -1
  52. data/lib/action_controller/metal.rb +16 -17
  53. data/lib/action_controller/railtie.rb +49 -6
  54. data/lib/action_controller/railties/helpers.rb +1 -1
  55. data/lib/action_controller/renderer.rb +37 -13
  56. data/lib/action_controller/template_assertions.rb +1 -1
  57. data/lib/action_controller/test_case.rb +98 -68
  58. data/lib/action_controller.rb +4 -5
  59. data/lib/action_dispatch/http/cache.rb +45 -32
  60. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  61. data/lib/action_dispatch/http/content_security_policy.rb +69 -56
  62. data/lib/action_dispatch/http/filter_parameters.rb +14 -8
  63. data/lib/action_dispatch/http/filter_redirect.rb +2 -3
  64. data/lib/action_dispatch/http/headers.rb +4 -4
  65. data/lib/action_dispatch/http/mime_negotiation.rb +44 -16
  66. data/lib/action_dispatch/http/mime_type.rb +47 -30
  67. data/lib/action_dispatch/http/parameters.rb +18 -27
  68. data/lib/action_dispatch/http/permissions_policy.rb +173 -0
  69. data/lib/action_dispatch/http/request.rb +49 -35
  70. data/lib/action_dispatch/http/response.rb +34 -26
  71. data/lib/action_dispatch/http/upload.rb +9 -1
  72. data/lib/action_dispatch/http/url.rb +86 -94
  73. data/lib/action_dispatch/journey/formatter.rb +55 -31
  74. data/lib/action_dispatch/journey/gtg/builder.rb +30 -46
  75. data/lib/action_dispatch/journey/gtg/simulator.rb +15 -8
  76. data/lib/action_dispatch/journey/gtg/transition_table.rb +78 -21
  77. data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
  78. data/lib/action_dispatch/journey/nodes/node.rb +83 -16
  79. data/lib/action_dispatch/journey/parser.rb +13 -13
  80. data/lib/action_dispatch/journey/parser.y +1 -1
  81. data/lib/action_dispatch/journey/path/pattern.rb +42 -34
  82. data/lib/action_dispatch/journey/route.rb +14 -31
  83. data/lib/action_dispatch/journey/router/utils.rb +16 -14
  84. data/lib/action_dispatch/journey/router.rb +27 -35
  85. data/lib/action_dispatch/journey/routes.rb +3 -5
  86. data/lib/action_dispatch/journey/scanner.rb +10 -4
  87. data/lib/action_dispatch/journey/visitors.rb +1 -4
  88. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  89. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  90. data/lib/action_dispatch/journey.rb +0 -2
  91. data/lib/action_dispatch/middleware/actionable_exceptions.rb +45 -0
  92. data/lib/action_dispatch/middleware/callbacks.rb +2 -4
  93. data/lib/action_dispatch/middleware/cookies.rb +136 -113
  94. data/lib/action_dispatch/middleware/debug_exceptions.rb +47 -68
  95. data/lib/action_dispatch/middleware/debug_locks.rb +8 -8
  96. data/lib/action_dispatch/middleware/debug_view.rb +66 -0
  97. data/lib/action_dispatch/middleware/exception_wrapper.rb +79 -30
  98. data/lib/action_dispatch/middleware/executor.rb +4 -1
  99. data/lib/action_dispatch/middleware/flash.rb +10 -12
  100. data/lib/action_dispatch/middleware/host_authorization.rb +159 -0
  101. data/lib/action_dispatch/middleware/public_exceptions.rb +6 -3
  102. data/lib/action_dispatch/middleware/remote_ip.rb +30 -20
  103. data/lib/action_dispatch/middleware/request_id.rb +5 -6
  104. data/lib/action_dispatch/middleware/server_timing.rb +33 -0
  105. data/lib/action_dispatch/middleware/session/abstract_store.rb +16 -3
  106. data/lib/action_dispatch/middleware/session/cache_store.rb +11 -6
  107. data/lib/action_dispatch/middleware/session/cookie_store.rb +24 -19
  108. data/lib/action_dispatch/middleware/show_exceptions.rb +20 -11
  109. data/lib/action_dispatch/middleware/ssl.rb +20 -15
  110. data/lib/action_dispatch/middleware/stack.rb +79 -7
  111. data/lib/action_dispatch/middleware/static.rb +150 -94
  112. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  113. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  114. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  115. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +6 -11
  116. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  117. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +4 -2
  118. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +46 -36
  119. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +8 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +7 -0
  121. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +25 -6
  122. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
  123. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +9 -6
  124. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +4 -1
  125. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +121 -15
  126. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  128. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +5 -5
  129. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +4 -4
  130. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +5 -5
  131. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
  132. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +16 -2
  133. data/lib/action_dispatch/railtie.rb +16 -4
  134. data/lib/action_dispatch/request/session.rb +59 -22
  135. data/lib/action_dispatch/request/utils.rb +28 -2
  136. data/lib/action_dispatch/routing/inspector.rb +102 -54
  137. data/lib/action_dispatch/routing/mapper.rb +184 -156
  138. data/lib/action_dispatch/routing/polymorphic_routes.rb +21 -19
  139. data/lib/action_dispatch/routing/redirection.rb +4 -6
  140. data/lib/action_dispatch/routing/route_set.rb +83 -73
  141. data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
  142. data/lib/action_dispatch/routing/url_for.rb +2 -3
  143. data/lib/action_dispatch/routing.rb +23 -22
  144. data/lib/action_dispatch/system_test_case.rb +65 -16
  145. data/lib/action_dispatch/system_testing/browser.rb +43 -16
  146. data/lib/action_dispatch/system_testing/driver.rb +42 -10
  147. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +58 -12
  148. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +3 -10
  149. data/lib/action_dispatch/testing/assertion_response.rb +0 -1
  150. data/lib/action_dispatch/testing/assertions/response.rb +4 -7
  151. data/lib/action_dispatch/testing/assertions/routing.rb +20 -8
  152. data/lib/action_dispatch/testing/assertions.rb +3 -6
  153. data/lib/action_dispatch/testing/integration.rb +61 -30
  154. data/lib/action_dispatch/testing/request_encoder.rb +2 -2
  155. data/lib/action_dispatch/testing/test_process.rb +8 -6
  156. data/lib/action_dispatch/testing/test_request.rb +3 -3
  157. data/lib/action_dispatch/testing/test_response.rb +4 -32
  158. data/lib/action_dispatch.rb +15 -7
  159. data/lib/action_pack/gem_version.rb +4 -4
  160. data/lib/action_pack.rb +1 -1
  161. metadata +44 -25
  162. data/lib/action_controller/metal/force_ssl.rb +0 -99
  163. data/lib/action_dispatch/http/parameter_filter.rb +0 -86
  164. data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
  165. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -49
  166. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -120
  167. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +0 -26
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+ require "uri"
5
+ require "active_support/actionable_error"
6
+
7
+ module ActionDispatch
8
+ class ActionableExceptions # :nodoc:
9
+ cattr_accessor :endpoint, default: "/rails/actions"
10
+
11
+ def initialize(app)
12
+ @app = app
13
+ end
14
+
15
+ def call(env)
16
+ request = ActionDispatch::Request.new(env)
17
+ return @app.call(env) unless actionable_request?(request)
18
+
19
+ ActiveSupport::ActionableError.dispatch(request.params[:error].to_s.safe_constantize, request.params[:action])
20
+
21
+ redirect_to request.params[:location]
22
+ end
23
+
24
+ private
25
+ def actionable_request?(request)
26
+ request.get_header("action_dispatch.show_detailed_exceptions") && request.post? && request.path == endpoint
27
+ end
28
+
29
+ def redirect_to(location)
30
+ uri = URI.parse location
31
+
32
+ if uri.relative? || uri.scheme == "http" || uri.scheme == "https"
33
+ body = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(location)}\">redirected</a>.</body></html>"
34
+ else
35
+ return [400, { "Content-Type" => "text/plain" }, ["Invalid redirection URI"]]
36
+ end
37
+
38
+ [302, {
39
+ "Content-Type" => "text/html; charset=#{Response.default_charset}",
40
+ "Content-Length" => body.bytesize.to_s,
41
+ "Location" => location,
42
+ }, [body]]
43
+ end
44
+ end
45
+ end
@@ -24,10 +24,8 @@ module ActionDispatch
24
24
  def call(env)
25
25
  error = nil
26
26
  result = run_callbacks :call do
27
- begin
28
- @app.call(env)
29
- rescue => error
30
- end
27
+ @app.call(env)
28
+ rescue => error
31
29
  end
32
30
  raise error if error
33
31
  result
@@ -7,9 +7,9 @@ require "active_support/json"
7
7
  require "rack/utils"
8
8
 
9
9
  module ActionDispatch
10
- class Request
10
+ module RequestCookieMethods
11
11
  def cookie_jar
12
- fetch_header("action_dispatch.cookies".freeze) do
12
+ fetch_header("action_dispatch.cookies") do
13
13
  self.cookie_jar = Cookies::CookieJar.build(self, cookies)
14
14
  end
15
15
  end
@@ -22,11 +22,11 @@ module ActionDispatch
22
22
  }
23
23
 
24
24
  def have_cookie_jar?
25
- has_header? "action_dispatch.cookies".freeze
25
+ has_header? "action_dispatch.cookies"
26
26
  end
27
27
 
28
28
  def cookie_jar=(jar)
29
- set_header "action_dispatch.cookies".freeze, jar
29
+ set_header "action_dispatch.cookies", jar
30
30
  end
31
31
 
32
32
  def key_generator
@@ -61,10 +61,6 @@ module ActionDispatch
61
61
  get_header Cookies::SIGNED_COOKIE_DIGEST
62
62
  end
63
63
 
64
- def secret_token
65
- get_header Cookies::SECRET_TOKEN
66
- end
67
-
68
64
  def secret_key_base
69
65
  get_header Cookies::SECRET_KEY_BASE
70
66
  end
@@ -73,6 +69,10 @@ module ActionDispatch
73
69
  get_header Cookies::COOKIES_SERIALIZER
74
70
  end
75
71
 
72
+ def cookies_same_site_protection
73
+ get_header(Cookies::COOKIES_SAME_SITE_PROTECTION) || Proc.new { }
74
+ end
75
+
76
76
  def cookies_digest
77
77
  get_header Cookies::COOKIES_DIGEST
78
78
  end
@@ -81,14 +81,21 @@ module ActionDispatch
81
81
  get_header Cookies::COOKIES_ROTATIONS
82
82
  end
83
83
 
84
+ def use_cookies_with_metadata
85
+ get_header Cookies::USE_COOKIES_WITH_METADATA
86
+ end
87
+
84
88
  # :startdoc:
85
89
  end
86
90
 
87
- # \Cookies are read and written through ActionController#cookies.
91
+ ActiveSupport.on_load(:action_dispatch_request) do
92
+ include RequestCookieMethods
93
+ end
94
+
95
+ # Read and write data to cookies through ActionController#cookies.
88
96
  #
89
- # The cookies being read are the ones received along with the request, the cookies
90
- # being written will be sent out with the response. Reading a cookie does not get
91
- # the cookie object itself back, just the value it holds.
97
+ # When reading cookie data, the data is read from the HTTP request header, Cookie.
98
+ # When writing cookie data, the data is sent out in the HTTP response header, Set-Cookie.
92
99
  #
93
100
  # Examples of writing:
94
101
  #
@@ -96,7 +103,7 @@ module ActionDispatch
96
103
  # # This cookie will be deleted when the user's browser is closed.
97
104
  # cookies[:user_name] = "david"
98
105
  #
99
- # # Cookie values are String based. Other data types need to be serialized.
106
+ # # Cookie values are String-based. Other data types need to be serialized.
100
107
  # cookies[:lat_lon] = JSON.generate([47.68, -122.37])
101
108
  #
102
109
  # # Sets a cookie that expires in 1 hour.
@@ -150,8 +157,10 @@ module ActionDispatch
150
157
  # * <tt>:domain</tt> - The domain for which this cookie applies so you can
151
158
  # restrict to the domain level. If you use a schema like www.example.com
152
159
  # and want to share session with user.example.com set <tt>:domain</tt>
153
- # to <tt>:all</tt>. Make sure to specify the <tt>:domain</tt> option with
154
- # <tt>:all</tt> or <tt>Array</tt> again when deleting cookies.
160
+ # to <tt>:all</tt>. To support multiple domains, provide an array, and
161
+ # the first domain matching <tt>request.host</tt> will be used. Make
162
+ # sure to specify the <tt>:domain</tt> option with <tt>:all</tt> or
163
+ # <tt>Array</tt> again when deleting cookies.
155
164
  #
156
165
  # domain: nil # Does not set cookie domain. (default)
157
166
  # domain: :all # Allow the cookie for the top most level
@@ -168,20 +177,21 @@ module ActionDispatch
168
177
  # * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
169
178
  # only HTTP. Defaults to +false+.
170
179
  class Cookies
171
- HTTP_HEADER = "Set-Cookie".freeze
172
- GENERATOR_KEY = "action_dispatch.key_generator".freeze
173
- SIGNED_COOKIE_SALT = "action_dispatch.signed_cookie_salt".freeze
174
- ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt".freeze
175
- ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze
176
- AUTHENTICATED_ENCRYPTED_COOKIE_SALT = "action_dispatch.authenticated_encrypted_cookie_salt".freeze
177
- USE_AUTHENTICATED_COOKIE_ENCRYPTION = "action_dispatch.use_authenticated_cookie_encryption".freeze
178
- ENCRYPTED_COOKIE_CIPHER = "action_dispatch.encrypted_cookie_cipher".freeze
179
- SIGNED_COOKIE_DIGEST = "action_dispatch.signed_cookie_digest".freeze
180
- SECRET_TOKEN = "action_dispatch.secret_token".freeze
181
- SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze
182
- COOKIES_SERIALIZER = "action_dispatch.cookies_serializer".freeze
183
- COOKIES_DIGEST = "action_dispatch.cookies_digest".freeze
184
- COOKIES_ROTATIONS = "action_dispatch.cookies_rotations".freeze
180
+ HTTP_HEADER = "Set-Cookie"
181
+ GENERATOR_KEY = "action_dispatch.key_generator"
182
+ SIGNED_COOKIE_SALT = "action_dispatch.signed_cookie_salt"
183
+ ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt"
184
+ ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt"
185
+ AUTHENTICATED_ENCRYPTED_COOKIE_SALT = "action_dispatch.authenticated_encrypted_cookie_salt"
186
+ USE_AUTHENTICATED_COOKIE_ENCRYPTION = "action_dispatch.use_authenticated_cookie_encryption"
187
+ ENCRYPTED_COOKIE_CIPHER = "action_dispatch.encrypted_cookie_cipher"
188
+ SIGNED_COOKIE_DIGEST = "action_dispatch.signed_cookie_digest"
189
+ SECRET_KEY_BASE = "action_dispatch.secret_key_base"
190
+ COOKIES_SERIALIZER = "action_dispatch.cookies_serializer"
191
+ COOKIES_DIGEST = "action_dispatch.cookies_digest"
192
+ COOKIES_ROTATIONS = "action_dispatch.cookies_rotations"
193
+ COOKIES_SAME_SITE_PROTECTION = "action_dispatch.cookies_same_site_protection"
194
+ USE_COOKIES_WITH_METADATA = "action_dispatch.use_cookies_with_metadata"
185
195
 
186
196
  # Cookies can typically store 4096 bytes.
187
197
  MAX_COOKIE_SIZE = 4096
@@ -210,9 +220,6 @@ module ActionDispatch
210
220
  # the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
211
221
  # cookie was tampered with by the user (or a 3rd party), +nil+ will be returned.
212
222
  #
213
- # If +secret_key_base+ and +secrets.secret_token+ (deprecated) are both set,
214
- # legacy cookies signed with the old key generator will be transparently upgraded.
215
- #
216
223
  # This jar requires that you set a suitable secret for the verification on your app's +secret_key_base+.
217
224
  #
218
225
  # Example:
@@ -228,9 +235,6 @@ module ActionDispatch
228
235
  # Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
229
236
  # If the cookie was tampered with by the user (or a 3rd party), +nil+ will be returned.
230
237
  #
231
- # If +secret_key_base+ and +secrets.secret_token+ (deprecated) are both set,
232
- # legacy cookies signed with the old key generator will be transparently upgraded.
233
- #
234
238
  # If +config.action_dispatch.encrypted_cookie_salt+ and +config.action_dispatch.encrypted_signed_cookie_salt+
235
239
  # are both set, legacy cookies encrypted with HMAC AES-256-CBC will be transparently upgraded.
236
240
  #
@@ -258,11 +262,6 @@ module ActionDispatch
258
262
  end
259
263
 
260
264
  private
261
-
262
- def upgrade_legacy_signed_cookies?
263
- request.secret_token.present? && request.secret_key_base.present?
264
- end
265
-
266
265
  def upgrade_legacy_hmac_aes_cbc_cookies?
267
266
  request.secret_key_base.present? &&
268
267
  request.encrypted_signed_cookie_salt.present? &&
@@ -270,6 +269,12 @@ module ActionDispatch
270
269
  request.use_authenticated_cookie_encryption
271
270
  end
272
271
 
272
+ def prepare_upgrade_legacy_hmac_aes_cbc_cookies?
273
+ request.secret_key_base.present? &&
274
+ request.authenticated_encrypted_cookie_salt.present? &&
275
+ !request.use_authenticated_cookie_encryption
276
+ end
277
+
273
278
  def encrypted_cookie_cipher
274
279
  request.encrypted_cookie_cipher || "aes-256-gcm"
275
280
  end
@@ -279,7 +284,7 @@ module ActionDispatch
279
284
  end
280
285
  end
281
286
 
282
- class CookieJar #:nodoc:
287
+ class CookieJar # :nodoc:
283
288
  include Enumerable, ChainedCookieJars
284
289
 
285
290
  # This regular expression is used to split the levels of a domain.
@@ -297,9 +302,9 @@ module ActionDispatch
297
302
  DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/
298
303
 
299
304
  def self.build(req, cookies)
300
- new(req).tap do |hash|
301
- hash.update(cookies)
302
- end
305
+ jar = new(req)
306
+ jar.update(cookies)
307
+ jar
303
308
  end
304
309
 
305
310
  attr_reader :request
@@ -348,7 +353,7 @@ module ActionDispatch
348
353
 
349
354
  def update_cookies_from_jar
350
355
  request_jar = @request.cookie_jar.instance_variable_get(:@cookies)
351
- set_cookies = request_jar.reject { |k, _| @delete_cookies.key?(k) }
356
+ set_cookies = request_jar.reject { |k, _| @delete_cookies.key?(k) || @set_cookies.key?(k) }
352
357
 
353
358
  @cookies.update set_cookies if set_cookies
354
359
  end
@@ -357,28 +362,6 @@ module ActionDispatch
357
362
  @cookies.map { |k, v| "#{escape(k)}=#{escape(v)}" }.join "; "
358
363
  end
359
364
 
360
- def handle_options(options) # :nodoc:
361
- if options[:expires].respond_to?(:from_now)
362
- options[:expires] = options[:expires].from_now
363
- end
364
-
365
- options[:path] ||= "/"
366
-
367
- if options[:domain] == :all || options[:domain] == "all"
368
- # If there is a provided tld length then we use it otherwise default domain regexp.
369
- domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP
370
-
371
- # If host is not ip and matches domain regexp.
372
- # (ip confirms to domain regexp so we explicitly check for ip)
373
- options[:domain] = if (request.host !~ /^[\d.]+$/) && (request.host =~ domain_regexp)
374
- ".#{$&}"
375
- end
376
- elsif options[:domain].is_a? Array
377
- # If host matches one of the supplied domains without a dot in front of it.
378
- options[:domain] = options[:domain].find { |domain| request.host.include? domain.sub(/^\./, "") }
379
- end
380
- end
381
-
382
365
  # Sets the cookie named +name+. The second argument may be the cookie's
383
366
  # value or a hash of options as documented above.
384
367
  def []=(name, options)
@@ -438,7 +421,6 @@ module ActionDispatch
438
421
  mattr_accessor :always_write_cookie, default: false
439
422
 
440
423
  private
441
-
442
424
  def escape(string)
443
425
  ::Rack::Utils.escape(string)
444
426
  end
@@ -457,7 +439,35 @@ module ActionDispatch
457
439
  end
458
440
 
459
441
  def write_cookie?(cookie)
460
- request.ssl? || !cookie[:secure] || always_write_cookie
442
+ request.ssl? || !cookie[:secure] || always_write_cookie || request.host.end_with?(".onion")
443
+ end
444
+
445
+ def handle_options(options)
446
+ if options[:expires].respond_to?(:from_now)
447
+ options[:expires] = options[:expires].from_now
448
+ end
449
+
450
+ options[:path] ||= "/"
451
+
452
+ cookies_same_site_protection = request.cookies_same_site_protection
453
+ options[:same_site] ||= cookies_same_site_protection.call(request)
454
+
455
+ if options[:domain] == :all || options[:domain] == "all"
456
+ # If there is a provided tld length then we use it otherwise default domain regexp.
457
+ domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP
458
+
459
+ # If host is not ip and matches domain regexp.
460
+ # (ip confirms to domain regexp so we explicitly check for ip)
461
+ options[:domain] = if !request.host.match?(/^[\d.]+$/) && (request.host =~ domain_regexp)
462
+ ".#{$&}"
463
+ end
464
+ elsif options[:domain].is_a? Array
465
+ # If host matches one of the supplied domains.
466
+ options[:domain] = options[:domain].find do |domain|
467
+ domain = domain.delete_prefix(".")
468
+ request.host == domain || request.host.end_with?(".#{domain}")
469
+ end
470
+ end
461
471
  end
462
472
  end
463
473
 
@@ -470,7 +480,13 @@ module ActionDispatch
470
480
 
471
481
  def [](name)
472
482
  if data = @parent_jar[name.to_s]
473
- parse name, data
483
+ result = parse(name, data, purpose: "cookie.#{name}")
484
+
485
+ if result.nil?
486
+ parse(name, data)
487
+ else
488
+ result
489
+ end
474
490
  end
475
491
  end
476
492
 
@@ -481,7 +497,7 @@ module ActionDispatch
481
497
  options = { value: options }
482
498
  end
483
499
 
484
- commit(options)
500
+ commit(name, options)
485
501
  @parent_jar[name] = options
486
502
  end
487
503
 
@@ -490,28 +506,42 @@ module ActionDispatch
490
506
 
491
507
  private
492
508
  def expiry_options(options)
493
- if request.use_authenticated_cookie_encryption
494
- if options[:expires].respond_to?(:from_now)
495
- { expires_in: options[:expires] }
496
- else
497
- { expires_at: options[:expires] }
498
- end
509
+ if options[:expires].respond_to?(:from_now)
510
+ { expires_in: options[:expires] }
499
511
  else
500
- {}
512
+ { expires_at: options[:expires] }
501
513
  end
502
514
  end
503
515
 
504
- def parse(name, data); data; end
505
- def commit(options); end
516
+ def cookie_metadata(name, options)
517
+ expiry_options(options).tap do |metadata|
518
+ metadata[:purpose] = "cookie.#{name}" if request.use_cookies_with_metadata
519
+ end
520
+ end
521
+
522
+ def parse(name, data, purpose: nil); data; end
523
+ def commit(name, options); end
506
524
  end
507
525
 
508
526
  class PermanentCookieJar < AbstractCookieJar # :nodoc:
509
527
  private
510
- def commit(options)
528
+ def commit(name, options)
511
529
  options[:expires] = 20.years.from_now
512
530
  end
513
531
  end
514
532
 
533
+ class MarshalWithJsonFallback # :nodoc:
534
+ def self.load(value)
535
+ Marshal.load(value)
536
+ rescue TypeError => e
537
+ ActiveSupport::JSON.decode(value) rescue raise e
538
+ end
539
+
540
+ def self.dump(value)
541
+ Marshal.dump(value)
542
+ end
543
+ end
544
+
515
545
  class JsonSerializer # :nodoc:
516
546
  def self.load(value)
517
547
  ActiveSupport::JSON.decode(value)
@@ -523,7 +553,7 @@ module ActionDispatch
523
553
  end
524
554
 
525
555
  module SerializedCookieJars # :nodoc:
526
- MARSHAL_SIGNATURE = "\x04\x08".freeze
556
+ MARSHAL_SIGNATURE = "\x04\x08"
527
557
  SERIALIZER = ActiveSupport::MessageEncryptor::NullSerializer
528
558
 
529
559
  protected
@@ -542,9 +572,13 @@ module ActionDispatch
542
572
  if value
543
573
  case
544
574
  when needs_migration?(value)
545
- self[name] = Marshal.load(value)
575
+ Marshal.load(value).tap do |v|
576
+ self[name] = { value: v }
577
+ end
546
578
  when rotate
547
- self[name] = serializer.load(value)
579
+ serializer.load(value).tap do |v|
580
+ self[name] = { value: v }
581
+ end
548
582
  else
549
583
  serializer.load(value)
550
584
  end
@@ -555,7 +589,7 @@ module ActionDispatch
555
589
  serializer = request.cookies_serializer || :marshal
556
590
  case serializer
557
591
  when :marshal
558
- Marshal
592
+ MarshalWithJsonFallback
559
593
  when :json, :hybrid
560
594
  JsonSerializer
561
595
  else
@@ -577,24 +611,21 @@ module ActionDispatch
577
611
  secret = request.key_generator.generate_key(request.signed_cookie_salt)
578
612
  @verifier = ActiveSupport::MessageVerifier.new(secret, digest: signed_cookie_digest, serializer: SERIALIZER)
579
613
 
580
- request.cookies_rotations.signed.each do |*secrets, **options|
614
+ request.cookies_rotations.signed.each do |(*secrets)|
615
+ options = secrets.extract_options!
581
616
  @verifier.rotate(*secrets, serializer: SERIALIZER, **options)
582
617
  end
583
-
584
- if upgrade_legacy_signed_cookies?
585
- @verifier.rotate request.secret_token, serializer: SERIALIZER
586
- end
587
618
  end
588
619
 
589
620
  private
590
- def parse(name, signed_message)
621
+ def parse(name, signed_message, purpose: nil)
591
622
  deserialize(name) do |rotate|
592
- @verifier.verified(signed_message, on_rotation: rotate)
623
+ @verifier.verified(signed_message, on_rotation: rotate, purpose: purpose)
593
624
  end
594
625
  end
595
626
 
596
- def commit(options)
597
- options[:value] = @verifier.generate(serialize(options[:value]), expiry_options(options))
627
+ def commit(name, options)
628
+ options[:value] = @verifier.generate(serialize(options[:value]), **cookie_metadata(name, options))
598
629
 
599
630
  raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
600
631
  end
@@ -617,7 +648,8 @@ module ActionDispatch
617
648
  @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, cipher: "aes-256-cbc", serializer: SERIALIZER)
618
649
  end
619
650
 
620
- request.cookies_rotations.encrypted.each do |*secrets, **options|
651
+ request.cookies_rotations.encrypted.each do |(*secrets)|
652
+ options = secrets.extract_options!
621
653
  @encryptor.rotate(*secrets, serializer: SERIALIZER, **options)
622
654
  end
623
655
 
@@ -627,37 +659,28 @@ module ActionDispatch
627
659
  sign_secret = request.key_generator.generate_key(request.encrypted_signed_cookie_salt)
628
660
 
629
661
  @encryptor.rotate(secret, sign_secret, cipher: legacy_cipher, digest: digest, serializer: SERIALIZER)
630
- end
662
+ elsif prepare_upgrade_legacy_hmac_aes_cbc_cookies?
663
+ future_cipher = encrypted_cookie_cipher
664
+ secret = request.key_generator.generate_key(request.authenticated_encrypted_cookie_salt, ActiveSupport::MessageEncryptor.key_len(future_cipher))
631
665
 
632
- if upgrade_legacy_signed_cookies?
633
- @legacy_verifier = ActiveSupport::MessageVerifier.new(request.secret_token, digest: digest, serializer: SERIALIZER)
666
+ @encryptor.rotate(secret, nil, cipher: future_cipher, serializer: SERIALIZER)
634
667
  end
635
668
  end
636
669
 
637
670
  private
638
- def parse(name, encrypted_message)
671
+ def parse(name, encrypted_message, purpose: nil)
639
672
  deserialize(name) do |rotate|
640
- @encryptor.decrypt_and_verify(encrypted_message, on_rotation: rotate)
673
+ @encryptor.decrypt_and_verify(encrypted_message, on_rotation: rotate, purpose: purpose)
641
674
  end
642
675
  rescue ActiveSupport::MessageEncryptor::InvalidMessage, ActiveSupport::MessageVerifier::InvalidSignature
643
- parse_legacy_signed_message(name, encrypted_message)
676
+ nil
644
677
  end
645
678
 
646
- def commit(options)
647
- options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value]), expiry_options(options))
679
+ def commit(name, options)
680
+ options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value]), **cookie_metadata(name, options))
648
681
 
649
682
  raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
650
683
  end
651
-
652
- def parse_legacy_signed_message(name, legacy_signed_message)
653
- if defined?(@legacy_verifier)
654
- deserialize(name) do |rotate|
655
- rotate.call
656
-
657
- @legacy_verifier.verified(legacy_signed_message)
658
- end
659
- end
660
- end
661
684
  end
662
685
 
663
686
  def initialize(app)