actionpack 5.2.6 → 6.1.4.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 (155) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +327 -335
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -3
  5. data/lib/abstract_controller/base.rb +38 -4
  6. data/lib/abstract_controller/caching/fragments.rb +6 -22
  7. data/lib/abstract_controller/caching.rb +1 -1
  8. data/lib/abstract_controller/callbacks.rb +14 -2
  9. data/lib/abstract_controller/collector.rb +1 -2
  10. data/lib/abstract_controller/helpers.rb +106 -90
  11. data/lib/abstract_controller/railties/routes_helpers.rb +17 -1
  12. data/lib/abstract_controller/rendering.rb +9 -9
  13. data/lib/abstract_controller/translation.rb +11 -5
  14. data/lib/abstract_controller.rb +1 -0
  15. data/lib/action_controller/api.rb +4 -3
  16. data/lib/action_controller/base.rb +6 -9
  17. data/lib/action_controller/caching.rb +1 -3
  18. data/lib/action_controller/log_subscriber.rb +10 -7
  19. data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
  20. data/lib/action_controller/metal/conditional_get.rb +19 -5
  21. data/lib/action_controller/metal/content_security_policy.rb +1 -2
  22. data/lib/action_controller/metal/cookies.rb +3 -1
  23. data/lib/action_controller/metal/data_streaming.rb +6 -7
  24. data/lib/action_controller/metal/default_headers.rb +17 -0
  25. data/lib/action_controller/metal/etag_with_template_digest.rb +4 -6
  26. data/lib/action_controller/metal/exceptions.rb +56 -2
  27. data/lib/action_controller/metal/flash.rb +5 -5
  28. data/lib/action_controller/metal/head.rb +7 -4
  29. data/lib/action_controller/metal/helpers.rb +14 -5
  30. data/lib/action_controller/metal/http_authentication.rb +24 -23
  31. data/lib/action_controller/metal/implicit_render.rb +5 -15
  32. data/lib/action_controller/metal/instrumentation.rb +13 -14
  33. data/lib/action_controller/metal/live.rb +39 -32
  34. data/lib/action_controller/metal/logging.rb +20 -0
  35. data/lib/action_controller/metal/mime_responds.rb +19 -4
  36. data/lib/action_controller/metal/parameter_encoding.rb +35 -4
  37. data/lib/action_controller/metal/params_wrapper.rb +32 -22
  38. data/lib/action_controller/metal/permissions_policy.rb +46 -0
  39. data/lib/action_controller/metal/redirecting.rb +6 -6
  40. data/lib/action_controller/metal/renderers.rb +4 -4
  41. data/lib/action_controller/metal/rendering.rb +8 -3
  42. data/lib/action_controller/metal/request_forgery_protection.rb +26 -49
  43. data/lib/action_controller/metal/rescue.rb +1 -1
  44. data/lib/action_controller/metal/streaming.rb +0 -1
  45. data/lib/action_controller/metal/strong_parameters.rb +167 -58
  46. data/lib/action_controller/metal/url_for.rb +1 -1
  47. data/lib/action_controller/metal.rb +10 -8
  48. data/lib/action_controller/railties/helpers.rb +1 -1
  49. data/lib/action_controller/renderer.rb +37 -13
  50. data/lib/action_controller/template_assertions.rb +1 -1
  51. data/lib/action_controller/test_case.rb +71 -63
  52. data/lib/action_controller.rb +7 -4
  53. data/lib/action_dispatch/http/cache.rb +31 -27
  54. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  55. data/lib/action_dispatch/http/content_security_policy.rb +33 -19
  56. data/lib/action_dispatch/http/filter_parameters.rb +9 -8
  57. data/lib/action_dispatch/http/filter_redirect.rb +2 -3
  58. data/lib/action_dispatch/http/headers.rb +4 -4
  59. data/lib/action_dispatch/http/mime_negotiation.rb +26 -13
  60. data/lib/action_dispatch/http/mime_type.rb +43 -24
  61. data/lib/action_dispatch/http/parameters.rb +14 -23
  62. data/lib/action_dispatch/http/permissions_policy.rb +173 -0
  63. data/lib/action_dispatch/http/request.rb +45 -22
  64. data/lib/action_dispatch/http/response.rb +45 -25
  65. data/lib/action_dispatch/http/upload.rb +9 -1
  66. data/lib/action_dispatch/http/url.rb +82 -82
  67. data/lib/action_dispatch/journey/formatter.rb +55 -31
  68. data/lib/action_dispatch/journey/gtg/builder.rb +22 -37
  69. data/lib/action_dispatch/journey/gtg/simulator.rb +8 -7
  70. data/lib/action_dispatch/journey/gtg/transition_table.rb +6 -5
  71. data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
  72. data/lib/action_dispatch/journey/nodes/node.rb +13 -11
  73. data/lib/action_dispatch/journey/parser.rb +13 -13
  74. data/lib/action_dispatch/journey/parser.y +1 -1
  75. data/lib/action_dispatch/journey/path/pattern.rb +19 -21
  76. data/lib/action_dispatch/journey/route.rb +10 -20
  77. data/lib/action_dispatch/journey/router/utils.rb +14 -12
  78. data/lib/action_dispatch/journey/router.rb +26 -34
  79. data/lib/action_dispatch/journey/routes.rb +0 -2
  80. data/lib/action_dispatch/journey/scanner.rb +10 -4
  81. data/lib/action_dispatch/journey/visitors.rb +1 -4
  82. data/lib/action_dispatch/journey.rb +0 -2
  83. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  84. data/lib/action_dispatch/middleware/callbacks.rb +2 -4
  85. data/lib/action_dispatch/middleware/cookies.rb +128 -109
  86. data/lib/action_dispatch/middleware/debug_exceptions.rb +43 -66
  87. data/lib/action_dispatch/middleware/debug_locks.rb +5 -5
  88. data/lib/action_dispatch/middleware/debug_view.rb +66 -0
  89. data/lib/action_dispatch/middleware/exception_wrapper.rb +75 -30
  90. data/lib/action_dispatch/middleware/flash.rb +1 -1
  91. data/lib/action_dispatch/middleware/host_authorization.rb +141 -0
  92. data/lib/action_dispatch/middleware/public_exceptions.rb +6 -3
  93. data/lib/action_dispatch/middleware/remote_ip.rb +14 -16
  94. data/lib/action_dispatch/middleware/request_id.rb +5 -6
  95. data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -3
  96. data/lib/action_dispatch/middleware/session/cookie_store.rb +3 -9
  97. data/lib/action_dispatch/middleware/show_exceptions.rb +3 -2
  98. data/lib/action_dispatch/middleware/ssl.rb +20 -15
  99. data/lib/action_dispatch/middleware/stack.rb +56 -2
  100. data/lib/action_dispatch/middleware/static.rb +153 -93
  101. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  102. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  103. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  104. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +3 -1
  105. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  106. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +4 -2
  107. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +45 -35
  108. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -0
  109. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -0
  110. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +23 -4
  111. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
  112. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +6 -3
  113. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +4 -1
  114. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +104 -8
  115. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  116. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  117. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +2 -2
  118. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -1
  119. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +2 -2
  120. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  121. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +24 -1
  122. data/lib/action_dispatch/railtie.rb +8 -2
  123. data/lib/action_dispatch/request/session.rb +11 -10
  124. data/lib/action_dispatch/request/utils.rb +26 -2
  125. data/lib/action_dispatch/routing/inspector.rb +100 -52
  126. data/lib/action_dispatch/routing/mapper.rb +155 -103
  127. data/lib/action_dispatch/routing/polymorphic_routes.rb +13 -15
  128. data/lib/action_dispatch/routing/redirection.rb +4 -4
  129. data/lib/action_dispatch/routing/route_set.rb +71 -69
  130. data/lib/action_dispatch/routing/url_for.rb +2 -2
  131. data/lib/action_dispatch/routing.rb +21 -20
  132. data/lib/action_dispatch/system_test_case.rb +54 -11
  133. data/lib/action_dispatch/system_testing/browser.rb +53 -16
  134. data/lib/action_dispatch/system_testing/driver.rb +11 -3
  135. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +49 -7
  136. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +8 -10
  137. data/lib/action_dispatch/testing/assertion_response.rb +0 -1
  138. data/lib/action_dispatch/testing/assertions/response.rb +4 -7
  139. data/lib/action_dispatch/testing/assertions/routing.rb +20 -8
  140. data/lib/action_dispatch/testing/assertions.rb +1 -1
  141. data/lib/action_dispatch/testing/integration.rb +60 -28
  142. data/lib/action_dispatch/testing/request_encoder.rb +2 -2
  143. data/lib/action_dispatch/testing/test_process.rb +29 -4
  144. data/lib/action_dispatch/testing/test_request.rb +3 -3
  145. data/lib/action_dispatch/testing/test_response.rb +4 -32
  146. data/lib/action_dispatch.rb +9 -3
  147. data/lib/action_pack/gem_version.rb +4 -4
  148. data/lib/action_pack.rb +1 -1
  149. metadata +35 -23
  150. data/lib/action_controller/metal/force_ssl.rb +0 -99
  151. data/lib/action_dispatch/http/parameter_filter.rb +0 -86
  152. data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
  153. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -49
  154. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -120
  155. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +0 -26
@@ -9,7 +9,7 @@ require "rack/utils"
9
9
  module ActionDispatch
10
10
  class Request
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,17 @@ 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
+ # Read and write data to cookies through ActionController#cookies.
88
92
  #
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.
93
+ # When reading cookie data, the data is read from the HTTP request header, Cookie.
94
+ # When writing cookie data, the data is sent out in the HTTP response header, Set-Cookie.
92
95
  #
93
96
  # Examples of writing:
94
97
  #
@@ -150,8 +153,10 @@ module ActionDispatch
150
153
  # * <tt>:domain</tt> - The domain for which this cookie applies so you can
151
154
  # restrict to the domain level. If you use a schema like www.example.com
152
155
  # 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.
156
+ # to <tt>:all</tt>. To support multiple domains, provide an array, and
157
+ # the first domain matching <tt>request.host</tt> will be used. Make
158
+ # sure to specify the <tt>:domain</tt> option with <tt>:all</tt> or
159
+ # <tt>Array</tt> again when deleting cookies.
155
160
  #
156
161
  # domain: nil # Does not set cookie domain. (default)
157
162
  # domain: :all # Allow the cookie for the top most level
@@ -168,20 +173,21 @@ module ActionDispatch
168
173
  # * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
169
174
  # only HTTP. Defaults to +false+.
170
175
  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
176
+ HTTP_HEADER = "Set-Cookie"
177
+ GENERATOR_KEY = "action_dispatch.key_generator"
178
+ SIGNED_COOKIE_SALT = "action_dispatch.signed_cookie_salt"
179
+ ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt"
180
+ ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt"
181
+ AUTHENTICATED_ENCRYPTED_COOKIE_SALT = "action_dispatch.authenticated_encrypted_cookie_salt"
182
+ USE_AUTHENTICATED_COOKIE_ENCRYPTION = "action_dispatch.use_authenticated_cookie_encryption"
183
+ ENCRYPTED_COOKIE_CIPHER = "action_dispatch.encrypted_cookie_cipher"
184
+ SIGNED_COOKIE_DIGEST = "action_dispatch.signed_cookie_digest"
185
+ SECRET_KEY_BASE = "action_dispatch.secret_key_base"
186
+ COOKIES_SERIALIZER = "action_dispatch.cookies_serializer"
187
+ COOKIES_DIGEST = "action_dispatch.cookies_digest"
188
+ COOKIES_ROTATIONS = "action_dispatch.cookies_rotations"
189
+ COOKIES_SAME_SITE_PROTECTION = "action_dispatch.cookies_same_site_protection"
190
+ USE_COOKIES_WITH_METADATA = "action_dispatch.use_cookies_with_metadata"
185
191
 
186
192
  # Cookies can typically store 4096 bytes.
187
193
  MAX_COOKIE_SIZE = 4096
@@ -210,9 +216,6 @@ module ActionDispatch
210
216
  # the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
211
217
  # cookie was tampered with by the user (or a 3rd party), +nil+ will be returned.
212
218
  #
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
219
  # This jar requires that you set a suitable secret for the verification on your app's +secret_key_base+.
217
220
  #
218
221
  # Example:
@@ -228,9 +231,6 @@ module ActionDispatch
228
231
  # Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
229
232
  # If the cookie was tampered with by the user (or a 3rd party), +nil+ will be returned.
230
233
  #
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
234
  # If +config.action_dispatch.encrypted_cookie_salt+ and +config.action_dispatch.encrypted_signed_cookie_salt+
235
235
  # are both set, legacy cookies encrypted with HMAC AES-256-CBC will be transparently upgraded.
236
236
  #
@@ -258,11 +258,6 @@ module ActionDispatch
258
258
  end
259
259
 
260
260
  private
261
-
262
- def upgrade_legacy_signed_cookies?
263
- request.secret_token.present? && request.secret_key_base.present?
264
- end
265
-
266
261
  def upgrade_legacy_hmac_aes_cbc_cookies?
267
262
  request.secret_key_base.present? &&
268
263
  request.encrypted_signed_cookie_salt.present? &&
@@ -270,6 +265,12 @@ module ActionDispatch
270
265
  request.use_authenticated_cookie_encryption
271
266
  end
272
267
 
268
+ def prepare_upgrade_legacy_hmac_aes_cbc_cookies?
269
+ request.secret_key_base.present? &&
270
+ request.authenticated_encrypted_cookie_salt.present? &&
271
+ !request.use_authenticated_cookie_encryption
272
+ end
273
+
273
274
  def encrypted_cookie_cipher
274
275
  request.encrypted_cookie_cipher || "aes-256-gcm"
275
276
  end
@@ -297,9 +298,9 @@ module ActionDispatch
297
298
  DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/
298
299
 
299
300
  def self.build(req, cookies)
300
- new(req).tap do |hash|
301
- hash.update(cookies)
302
- end
301
+ jar = new(req)
302
+ jar.update(cookies)
303
+ jar
303
304
  end
304
305
 
305
306
  attr_reader :request
@@ -348,7 +349,7 @@ module ActionDispatch
348
349
 
349
350
  def update_cookies_from_jar
350
351
  request_jar = @request.cookie_jar.instance_variable_get(:@cookies)
351
- set_cookies = request_jar.reject { |k, _| @delete_cookies.key?(k) }
352
+ set_cookies = request_jar.reject { |k, _| @delete_cookies.key?(k) || @set_cookies.key?(k) }
352
353
 
353
354
  @cookies.update set_cookies if set_cookies
354
355
  end
@@ -357,28 +358,6 @@ module ActionDispatch
357
358
  @cookies.map { |k, v| "#{escape(k)}=#{escape(v)}" }.join "; "
358
359
  end
359
360
 
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
361
  # Sets the cookie named +name+. The second argument may be the cookie's
383
362
  # value or a hash of options as documented above.
384
363
  def []=(name, options)
@@ -438,7 +417,6 @@ module ActionDispatch
438
417
  mattr_accessor :always_write_cookie, default: false
439
418
 
440
419
  private
441
-
442
420
  def escape(string)
443
421
  ::Rack::Utils.escape(string)
444
422
  end
@@ -459,6 +437,34 @@ module ActionDispatch
459
437
  def write_cookie?(cookie)
460
438
  request.ssl? || !cookie[:secure] || always_write_cookie
461
439
  end
440
+
441
+ def handle_options(options)
442
+ if options[:expires].respond_to?(:from_now)
443
+ options[:expires] = options[:expires].from_now
444
+ end
445
+
446
+ options[:path] ||= "/"
447
+
448
+ cookies_same_site_protection = request.cookies_same_site_protection
449
+ options[:same_site] ||= cookies_same_site_protection.call(request)
450
+
451
+ if options[:domain] == :all || options[:domain] == "all"
452
+ # If there is a provided tld length then we use it otherwise default domain regexp.
453
+ domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP
454
+
455
+ # If host is not ip and matches domain regexp.
456
+ # (ip confirms to domain regexp so we explicitly check for ip)
457
+ options[:domain] = if !request.host.match?(/^[\d.]+$/) && (request.host =~ domain_regexp)
458
+ ".#{$&}"
459
+ end
460
+ elsif options[:domain].is_a? Array
461
+ # If host matches one of the supplied domains.
462
+ options[:domain] = options[:domain].find do |domain|
463
+ domain = domain.delete_prefix(".")
464
+ request.host == domain || request.host.end_with?(".#{domain}")
465
+ end
466
+ end
467
+ end
462
468
  end
463
469
 
464
470
  class AbstractCookieJar # :nodoc:
@@ -470,7 +476,13 @@ module ActionDispatch
470
476
 
471
477
  def [](name)
472
478
  if data = @parent_jar[name.to_s]
473
- parse name, data
479
+ result = parse(name, data, purpose: "cookie.#{name}")
480
+
481
+ if result.nil?
482
+ parse(name, data)
483
+ else
484
+ result
485
+ end
474
486
  end
475
487
  end
476
488
 
@@ -481,7 +493,7 @@ module ActionDispatch
481
493
  options = { value: options }
482
494
  end
483
495
 
484
- commit(options)
496
+ commit(name, options)
485
497
  @parent_jar[name] = options
486
498
  end
487
499
 
@@ -490,28 +502,42 @@ module ActionDispatch
490
502
 
491
503
  private
492
504
  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
505
+ if options[:expires].respond_to?(:from_now)
506
+ { expires_in: options[:expires] }
499
507
  else
500
- {}
508
+ { expires_at: options[:expires] }
501
509
  end
502
510
  end
503
511
 
504
- def parse(name, data); data; end
505
- def commit(options); end
512
+ def cookie_metadata(name, options)
513
+ expiry_options(options).tap do |metadata|
514
+ metadata[:purpose] = "cookie.#{name}" if request.use_cookies_with_metadata
515
+ end
516
+ end
517
+
518
+ def parse(name, data, purpose: nil); data; end
519
+ def commit(name, options); end
506
520
  end
507
521
 
508
522
  class PermanentCookieJar < AbstractCookieJar # :nodoc:
509
523
  private
510
- def commit(options)
524
+ def commit(name, options)
511
525
  options[:expires] = 20.years.from_now
512
526
  end
513
527
  end
514
528
 
529
+ class MarshalWithJsonFallback # :nodoc:
530
+ def self.load(value)
531
+ Marshal.load(value)
532
+ rescue TypeError => e
533
+ ActiveSupport::JSON.decode(value) rescue raise e
534
+ end
535
+
536
+ def self.dump(value)
537
+ Marshal.dump(value)
538
+ end
539
+ end
540
+
515
541
  class JsonSerializer # :nodoc:
516
542
  def self.load(value)
517
543
  ActiveSupport::JSON.decode(value)
@@ -523,7 +549,7 @@ module ActionDispatch
523
549
  end
524
550
 
525
551
  module SerializedCookieJars # :nodoc:
526
- MARSHAL_SIGNATURE = "\x04\x08".freeze
552
+ MARSHAL_SIGNATURE = "\x04\x08"
527
553
  SERIALIZER = ActiveSupport::MessageEncryptor::NullSerializer
528
554
 
529
555
  protected
@@ -542,9 +568,13 @@ module ActionDispatch
542
568
  if value
543
569
  case
544
570
  when needs_migration?(value)
545
- self[name] = Marshal.load(value)
571
+ Marshal.load(value).tap do |v|
572
+ self[name] = { value: v }
573
+ end
546
574
  when rotate
547
- self[name] = serializer.load(value)
575
+ serializer.load(value).tap do |v|
576
+ self[name] = { value: v }
577
+ end
548
578
  else
549
579
  serializer.load(value)
550
580
  end
@@ -555,7 +585,7 @@ module ActionDispatch
555
585
  serializer = request.cookies_serializer || :marshal
556
586
  case serializer
557
587
  when :marshal
558
- Marshal
588
+ MarshalWithJsonFallback
559
589
  when :json, :hybrid
560
590
  JsonSerializer
561
591
  else
@@ -577,24 +607,21 @@ module ActionDispatch
577
607
  secret = request.key_generator.generate_key(request.signed_cookie_salt)
578
608
  @verifier = ActiveSupport::MessageVerifier.new(secret, digest: signed_cookie_digest, serializer: SERIALIZER)
579
609
 
580
- request.cookies_rotations.signed.each do |*secrets, **options|
610
+ request.cookies_rotations.signed.each do |(*secrets)|
611
+ options = secrets.extract_options!
581
612
  @verifier.rotate(*secrets, serializer: SERIALIZER, **options)
582
613
  end
583
-
584
- if upgrade_legacy_signed_cookies?
585
- @verifier.rotate request.secret_token, serializer: SERIALIZER
586
- end
587
614
  end
588
615
 
589
616
  private
590
- def parse(name, signed_message)
617
+ def parse(name, signed_message, purpose: nil)
591
618
  deserialize(name) do |rotate|
592
- @verifier.verified(signed_message, on_rotation: rotate)
619
+ @verifier.verified(signed_message, on_rotation: rotate, purpose: purpose)
593
620
  end
594
621
  end
595
622
 
596
- def commit(options)
597
- options[:value] = @verifier.generate(serialize(options[:value]), expiry_options(options))
623
+ def commit(name, options)
624
+ options[:value] = @verifier.generate(serialize(options[:value]), **cookie_metadata(name, options))
598
625
 
599
626
  raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
600
627
  end
@@ -617,7 +644,8 @@ module ActionDispatch
617
644
  @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, cipher: "aes-256-cbc", serializer: SERIALIZER)
618
645
  end
619
646
 
620
- request.cookies_rotations.encrypted.each do |*secrets, **options|
647
+ request.cookies_rotations.encrypted.each do |(*secrets)|
648
+ options = secrets.extract_options!
621
649
  @encryptor.rotate(*secrets, serializer: SERIALIZER, **options)
622
650
  end
623
651
 
@@ -627,37 +655,28 @@ module ActionDispatch
627
655
  sign_secret = request.key_generator.generate_key(request.encrypted_signed_cookie_salt)
628
656
 
629
657
  @encryptor.rotate(secret, sign_secret, cipher: legacy_cipher, digest: digest, serializer: SERIALIZER)
630
- end
658
+ elsif prepare_upgrade_legacy_hmac_aes_cbc_cookies?
659
+ future_cipher = encrypted_cookie_cipher
660
+ secret = request.key_generator.generate_key(request.authenticated_encrypted_cookie_salt, ActiveSupport::MessageEncryptor.key_len(future_cipher))
631
661
 
632
- if upgrade_legacy_signed_cookies?
633
- @legacy_verifier = ActiveSupport::MessageVerifier.new(request.secret_token, digest: digest, serializer: SERIALIZER)
662
+ @encryptor.rotate(secret, nil, cipher: future_cipher, serializer: SERIALIZER)
634
663
  end
635
664
  end
636
665
 
637
666
  private
638
- def parse(name, encrypted_message)
667
+ def parse(name, encrypted_message, purpose: nil)
639
668
  deserialize(name) do |rotate|
640
- @encryptor.decrypt_and_verify(encrypted_message, on_rotation: rotate)
669
+ @encryptor.decrypt_and_verify(encrypted_message, on_rotation: rotate, purpose: purpose)
641
670
  end
642
671
  rescue ActiveSupport::MessageEncryptor::InvalidMessage, ActiveSupport::MessageVerifier::InvalidSignature
643
- parse_legacy_signed_message(name, encrypted_message)
672
+ nil
644
673
  end
645
674
 
646
- def commit(options)
647
- options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value]), expiry_options(options))
675
+ def commit(name, options)
676
+ options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value]), **cookie_metadata(name, options))
648
677
 
649
678
  raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
650
679
  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
680
  end
662
681
 
663
682
  def initialize(app)
@@ -3,57 +3,25 @@
3
3
  require "action_dispatch/http/request"
4
4
  require "action_dispatch/middleware/exception_wrapper"
5
5
  require "action_dispatch/routing/inspector"
6
- require "action_view"
7
- require "action_view/base"
8
6
 
9
- require "pp"
7
+ require "action_view"
10
8
 
11
9
  module ActionDispatch
12
10
  # This middleware is responsible for logging exceptions and
13
11
  # showing a debugging page in case the request is local.
14
12
  class DebugExceptions
15
- RESCUES_TEMPLATE_PATH = File.expand_path("templates", __dir__)
16
-
17
- class DebugView < ActionView::Base
18
- def debug_params(params)
19
- clean_params = params.clone
20
- clean_params.delete("action")
21
- clean_params.delete("controller")
22
-
23
- if clean_params.empty?
24
- "None"
25
- else
26
- PP.pp(clean_params, "".dup, 200)
27
- end
28
- end
29
-
30
- def debug_headers(headers)
31
- if headers.present?
32
- headers.inspect.gsub(",", ",\n")
33
- else
34
- "None"
35
- end
36
- end
37
-
38
- def debug_hash(object)
39
- object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
40
- end
41
-
42
- def render(*)
43
- logger = ActionView::Base.logger
13
+ cattr_reader :interceptors, instance_accessor: false, default: []
44
14
 
45
- if logger && logger.respond_to?(:silence)
46
- logger.silence { super }
47
- else
48
- super
49
- end
50
- end
15
+ def self.register_interceptor(object = nil, &block)
16
+ interceptor = object || block
17
+ interceptors << interceptor
51
18
  end
52
19
 
53
- def initialize(app, routes_app = nil, response_format = :default)
20
+ def initialize(app, routes_app = nil, response_format = :default, interceptors = self.class.interceptors)
54
21
  @app = app
55
22
  @routes_app = routes_app
56
23
  @response_format = response_format
24
+ @interceptors = interceptors
57
25
  end
58
26
 
59
27
  def call(env)
@@ -67,11 +35,22 @@ module ActionDispatch
67
35
 
68
36
  response
69
37
  rescue Exception => exception
38
+ invoke_interceptors(request, exception)
70
39
  raise exception unless request.show_exceptions?
71
40
  render_exception(request, exception)
72
41
  end
73
42
 
74
43
  private
44
+ def invoke_interceptors(request, exception)
45
+ backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
46
+ wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
47
+
48
+ @interceptors.each do |interceptor|
49
+ interceptor.call(request, exception)
50
+ rescue Exception
51
+ log_error(request, wrapper)
52
+ end
53
+ end
75
54
 
76
55
  def render_exception(request, exception)
77
56
  backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
@@ -79,7 +58,11 @@ module ActionDispatch
79
58
  log_error(request, wrapper)
80
59
 
81
60
  if request.get_header("action_dispatch.show_detailed_exceptions")
82
- content_type = request.formats.first
61
+ begin
62
+ content_type = request.formats.first
63
+ rescue ActionDispatch::Http::MimeNegotiation::InvalidType
64
+ content_type = Mime[:text]
65
+ end
83
66
 
84
67
  if api_request?(content_type)
85
68
  render_for_api_request(content_type, wrapper)
@@ -130,23 +113,13 @@ module ActionDispatch
130
113
  end
131
114
 
132
115
  def create_template(request, wrapper)
133
- traces = wrapper.traces
134
-
135
- trace_to_show = "Application Trace"
136
- if traces[trace_to_show].empty? && wrapper.rescue_template != "routing_error"
137
- trace_to_show = "Full Trace"
138
- end
139
-
140
- if source_to_show = traces[trace_to_show].first
141
- source_to_show_id = source_to_show[:id]
142
- end
143
-
144
- DebugView.new([RESCUES_TEMPLATE_PATH],
116
+ DebugView.new(
145
117
  request: request,
118
+ exception_wrapper: wrapper,
146
119
  exception: wrapper.exception,
147
- traces: traces,
148
- show_source_idx: source_to_show_id,
149
- trace_to_show: trace_to_show,
120
+ traces: wrapper.traces,
121
+ show_source_idx: wrapper.source_to_show_id,
122
+ trace_to_show: wrapper.trace_to_show,
150
123
  routes_inspector: routes_inspector(wrapper.exception),
151
124
  source_extracts: wrapper.source_extracts,
152
125
  line_number: wrapper.line_number,
@@ -160,27 +133,31 @@ module ActionDispatch
160
133
 
161
134
  def log_error(request, wrapper)
162
135
  logger = logger(request)
136
+
163
137
  return unless logger
164
138
 
165
139
  exception = wrapper.exception
140
+ trace = wrapper.exception_trace
166
141
 
167
- trace = wrapper.application_trace
168
- trace = wrapper.framework_trace if trace.empty?
142
+ message = []
143
+ message << " "
144
+ message << "#{exception.class} (#{exception.message}):"
145
+ message.concat(exception.annotated_source_code) if exception.respond_to?(:annotated_source_code)
146
+ message << " "
147
+ message.concat(trace)
169
148
 
170
- ActiveSupport::Deprecation.silence do
171
- logger.fatal " "
172
- logger.fatal "#{exception.class} (#{exception.message}):"
173
- log_array logger, exception.annoted_source_code if exception.respond_to?(:annoted_source_code)
174
- logger.fatal " "
175
- log_array logger, trace
176
- end
149
+ log_array(logger, message)
177
150
  end
178
151
 
179
152
  def log_array(logger, array)
153
+ lines = Array(array)
154
+
155
+ return if lines.empty?
156
+
180
157
  if logger.formatter && logger.formatter.respond_to?(:tags_text)
181
- logger.fatal array.join("\n#{logger.formatter.tags_text}")
158
+ logger.fatal lines.join("\n#{logger.formatter.tags_text}")
182
159
  else
183
- logger.fatal array.join("\n")
160
+ logger.fatal lines.join("\n")
184
161
  end
185
162
  end
186
163
 
@@ -32,7 +32,7 @@ module ActionDispatch
32
32
  req = ActionDispatch::Request.new env
33
33
 
34
34
  if req.get?
35
- path = req.path_info.chomp("/".freeze)
35
+ path = req.path_info.chomp("/")
36
36
  if path == @path
37
37
  return render_details(req)
38
38
  end
@@ -63,19 +63,19 @@ module ActionDispatch
63
63
 
64
64
  str = threads.map do |thread, info|
65
65
  if info[:exclusive]
66
- lock_state = "Exclusive".dup
66
+ lock_state = +"Exclusive"
67
67
  elsif info[:sharing] > 0
68
- lock_state = "Sharing".dup
68
+ lock_state = +"Sharing"
69
69
  lock_state << " x#{info[:sharing]}" if info[:sharing] > 1
70
70
  else
71
- lock_state = "No lock".dup
71
+ lock_state = +"No lock"
72
72
  end
73
73
 
74
74
  if info[:waiting]
75
75
  lock_state << " (yielded share)"
76
76
  end
77
77
 
78
- msg = "Thread #{info[:index]} [0x#{thread.__id__.to_s(16)} #{thread.status || 'dead'}] #{lock_state}\n".dup
78
+ msg = +"Thread #{info[:index]} [0x#{thread.__id__.to_s(16)} #{thread.status || 'dead'}] #{lock_state}\n"
79
79
 
80
80
  if info[:sleeper]
81
81
  msg << " Waiting in #{info[:sleeper]}"