actionpack 7.0.4.3 → 7.1.3.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 (140) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +380 -284
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -4
  5. data/lib/abstract_controller/base.rb +20 -11
  6. data/lib/abstract_controller/caching/fragments.rb +2 -0
  7. data/lib/abstract_controller/callbacks.rb +31 -6
  8. data/lib/abstract_controller/deprecator.rb +7 -0
  9. data/lib/abstract_controller/helpers.rb +75 -28
  10. data/lib/abstract_controller/railties/routes_helpers.rb +1 -16
  11. data/lib/abstract_controller/rendering.rb +12 -14
  12. data/lib/abstract_controller/translation.rb +9 -6
  13. data/lib/abstract_controller/url_for.rb +2 -0
  14. data/lib/abstract_controller.rb +6 -0
  15. data/lib/action_controller/api.rb +6 -4
  16. data/lib/action_controller/base.rb +3 -17
  17. data/lib/action_controller/caching.rb +2 -0
  18. data/lib/action_controller/deprecator.rb +7 -0
  19. data/lib/action_controller/form_builder.rb +2 -0
  20. data/lib/action_controller/log_subscriber.rb +16 -4
  21. data/lib/action_controller/metal/basic_implicit_render.rb +3 -1
  22. data/lib/action_controller/metal/conditional_get.rb +121 -123
  23. data/lib/action_controller/metal/content_security_policy.rb +5 -5
  24. data/lib/action_controller/metal/data_streaming.rb +20 -18
  25. data/lib/action_controller/metal/default_headers.rb +2 -0
  26. data/lib/action_controller/metal/etag_with_flash.rb +3 -1
  27. data/lib/action_controller/metal/etag_with_template_digest.rb +2 -0
  28. data/lib/action_controller/metal/exceptions.rb +8 -0
  29. data/lib/action_controller/metal/head.rb +9 -7
  30. data/lib/action_controller/metal/helpers.rb +3 -14
  31. data/lib/action_controller/metal/http_authentication.rb +17 -8
  32. data/lib/action_controller/metal/implicit_render.rb +5 -3
  33. data/lib/action_controller/metal/instrumentation.rb +8 -1
  34. data/lib/action_controller/metal/live.rb +25 -1
  35. data/lib/action_controller/metal/mime_responds.rb +2 -2
  36. data/lib/action_controller/metal/params_wrapper.rb +4 -2
  37. data/lib/action_controller/metal/permissions_policy.rb +2 -2
  38. data/lib/action_controller/metal/redirecting.rb +25 -8
  39. data/lib/action_controller/metal/renderers.rb +4 -4
  40. data/lib/action_controller/metal/rendering.rb +114 -9
  41. data/lib/action_controller/metal/request_forgery_protection.rb +144 -53
  42. data/lib/action_controller/metal/rescue.rb +6 -3
  43. data/lib/action_controller/metal/streaming.rb +71 -31
  44. data/lib/action_controller/metal/strong_parameters.rb +158 -101
  45. data/lib/action_controller/metal/url_for.rb +9 -4
  46. data/lib/action_controller/metal.rb +79 -21
  47. data/lib/action_controller/railtie.rb +24 -10
  48. data/lib/action_controller/renderer.rb +99 -85
  49. data/lib/action_controller/test_case.rb +15 -5
  50. data/lib/action_controller.rb +8 -1
  51. data/lib/action_dispatch/constants.rb +32 -0
  52. data/lib/action_dispatch/deprecator.rb +7 -0
  53. data/lib/action_dispatch/http/cache.rb +8 -10
  54. data/lib/action_dispatch/http/content_security_policy.rb +14 -9
  55. data/lib/action_dispatch/http/filter_parameters.rb +14 -28
  56. data/lib/action_dispatch/http/headers.rb +3 -1
  57. data/lib/action_dispatch/http/mime_negotiation.rb +22 -22
  58. data/lib/action_dispatch/http/mime_type.rb +35 -12
  59. data/lib/action_dispatch/http/mime_types.rb +3 -1
  60. data/lib/action_dispatch/http/parameters.rb +1 -1
  61. data/lib/action_dispatch/http/permissions_policy.rb +38 -23
  62. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  63. data/lib/action_dispatch/http/request.rb +63 -30
  64. data/lib/action_dispatch/http/response.rb +80 -63
  65. data/lib/action_dispatch/http/upload.rb +15 -2
  66. data/lib/action_dispatch/journey/formatter.rb +8 -2
  67. data/lib/action_dispatch/journey/path/pattern.rb +14 -14
  68. data/lib/action_dispatch/journey/route.rb +3 -2
  69. data/lib/action_dispatch/journey/router.rb +9 -8
  70. data/lib/action_dispatch/journey/routes.rb +2 -2
  71. data/lib/action_dispatch/log_subscriber.rb +23 -0
  72. data/lib/action_dispatch/middleware/actionable_exceptions.rb +5 -6
  73. data/lib/action_dispatch/middleware/assume_ssl.rb +24 -0
  74. data/lib/action_dispatch/middleware/callbacks.rb +2 -0
  75. data/lib/action_dispatch/middleware/cookies.rb +85 -102
  76. data/lib/action_dispatch/middleware/debug_exceptions.rb +26 -25
  77. data/lib/action_dispatch/middleware/debug_locks.rb +4 -1
  78. data/lib/action_dispatch/middleware/debug_view.rb +7 -2
  79. data/lib/action_dispatch/middleware/exception_wrapper.rb +186 -27
  80. data/lib/action_dispatch/middleware/executor.rb +1 -1
  81. data/lib/action_dispatch/middleware/flash.rb +7 -0
  82. data/lib/action_dispatch/middleware/host_authorization.rb +18 -8
  83. data/lib/action_dispatch/middleware/public_exceptions.rb +5 -3
  84. data/lib/action_dispatch/middleware/reloader.rb +7 -5
  85. data/lib/action_dispatch/middleware/remote_ip.rb +21 -20
  86. data/lib/action_dispatch/middleware/request_id.rb +4 -2
  87. data/lib/action_dispatch/middleware/server_timing.rb +4 -4
  88. data/lib/action_dispatch/middleware/session/abstract_store.rb +5 -0
  89. data/lib/action_dispatch/middleware/session/cache_store.rb +2 -0
  90. data/lib/action_dispatch/middleware/session/cookie_store.rb +11 -5
  91. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +3 -1
  92. data/lib/action_dispatch/middleware/show_exceptions.rb +25 -18
  93. data/lib/action_dispatch/middleware/ssl.rb +18 -6
  94. data/lib/action_dispatch/middleware/stack.rb +7 -2
  95. data/lib/action_dispatch/middleware/static.rb +14 -10
  96. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
  97. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +4 -4
  98. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
  99. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -3
  100. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -3
  101. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +7 -7
  102. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
  103. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +17 -0
  104. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +16 -12
  105. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -1
  106. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
  107. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +4 -4
  108. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  109. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  110. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +3 -0
  111. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +59 -41
  112. data/lib/action_dispatch/railtie.rb +14 -4
  113. data/lib/action_dispatch/request/session.rb +16 -6
  114. data/lib/action_dispatch/request/utils.rb +8 -3
  115. data/lib/action_dispatch/routing/inspector.rb +54 -6
  116. data/lib/action_dispatch/routing/mapper.rb +58 -24
  117. data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -0
  118. data/lib/action_dispatch/routing/redirection.rb +15 -6
  119. data/lib/action_dispatch/routing/route_set.rb +52 -22
  120. data/lib/action_dispatch/routing/routes_proxy.rb +10 -15
  121. data/lib/action_dispatch/routing/url_for.rb +26 -22
  122. data/lib/action_dispatch/routing.rb +7 -7
  123. data/lib/action_dispatch/system_test_case.rb +3 -3
  124. data/lib/action_dispatch/system_testing/browser.rb +20 -19
  125. data/lib/action_dispatch/system_testing/driver.rb +14 -22
  126. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +27 -16
  127. data/lib/action_dispatch/testing/assertion_response.rb +1 -1
  128. data/lib/action_dispatch/testing/assertions/response.rb +14 -7
  129. data/lib/action_dispatch/testing/assertions/routing.rb +67 -28
  130. data/lib/action_dispatch/testing/assertions.rb +3 -1
  131. data/lib/action_dispatch/testing/integration.rb +27 -17
  132. data/lib/action_dispatch/testing/request_encoder.rb +4 -1
  133. data/lib/action_dispatch/testing/test_process.rb +4 -3
  134. data/lib/action_dispatch/testing/test_request.rb +1 -1
  135. data/lib/action_dispatch/testing/test_response.rb +23 -9
  136. data/lib/action_dispatch.rb +37 -4
  137. data/lib/action_pack/gem_version.rb +4 -4
  138. data/lib/action_pack/version.rb +1 -1
  139. data/lib/action_pack.rb +1 -1
  140. metadata +65 -29
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionDispatch
4
+ class LogSubscriber < ActiveSupport::LogSubscriber
5
+ def redirect(event)
6
+ payload = event.payload
7
+
8
+ info { "Redirected to #{payload[:location]}" }
9
+
10
+ info do
11
+ status = payload[:status]
12
+
13
+ message = +"Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms"
14
+ message << "\n\n" if defined?(Rails.env) && Rails.env.development?
15
+
16
+ message
17
+ end
18
+ end
19
+ subscribe_log_level :redirect, :info
20
+ end
21
+ end
22
+
23
+ ActionDispatch::LogSubscriber.attach_to :action_dispatch
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "erb"
4
3
  require "uri"
5
4
  require "active_support/actionable_error"
6
5
 
@@ -30,15 +29,15 @@ module ActionDispatch
30
29
  uri = URI.parse location
31
30
 
32
31
  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>"
32
+ body = ""
34
33
  else
35
- return [400, { "Content-Type" => "text/plain" }, ["Invalid redirection URI"]]
34
+ return [400, { Rack::CONTENT_TYPE => "text/plain; charset=utf-8" }, ["Invalid redirection URI"]]
36
35
  end
37
36
 
38
37
  [302, {
39
- "Content-Type" => "text/html; charset=#{Response.default_charset}",
40
- "Content-Length" => body.bytesize.to_s,
41
- "Location" => location,
38
+ Rack::CONTENT_TYPE => "text/html; charset=#{Response.default_charset}",
39
+ Rack::CONTENT_LENGTH => body.bytesize.to_s,
40
+ ActionDispatch::Constants::LOCATION => location,
42
41
  }, [body]]
43
42
  end
44
43
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionDispatch
4
+ # = Action Dispatch \AssumeSSL
5
+ #
6
+ # When proxying through a load balancer that terminates SSL, the forwarded request will appear
7
+ # as though it's HTTP instead of HTTPS to the application. This makes redirects and cookie
8
+ # security target HTTP instead of HTTPS. This middleware makes the server assume that the
9
+ # proxy already terminated SSL, and that the request really is HTTPS.
10
+ class AssumeSSL
11
+ def initialize(app)
12
+ @app = app
13
+ end
14
+
15
+ def call(env)
16
+ env["HTTPS"] = "on"
17
+ env["HTTP_X_FORWARDED_PORT"] = "443"
18
+ env["HTTP_X_FORWARDED_PROTO"] = "https"
19
+ env["rack.url_scheme"] = "https"
20
+
21
+ @app.call(env)
22
+ end
23
+ end
24
+ end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionDispatch
4
+ # = Action Dispatch \Callbacks
5
+ #
4
6
  # Provides callbacks to be executed before and after dispatching the request.
5
7
  class Callbacks
6
8
  include ActiveSupport::Callbacks
@@ -70,7 +70,7 @@ module ActionDispatch
70
70
  end
71
71
 
72
72
  def cookies_same_site_protection
73
- get_header(Cookies::COOKIES_SAME_SITE_PROTECTION) || Proc.new { }
73
+ get_header(Cookies::COOKIES_SAME_SITE_PROTECTION)&.call(self)
74
74
  end
75
75
 
76
76
  def cookies_digest
@@ -92,10 +92,10 @@ module ActionDispatch
92
92
  include RequestCookieMethods
93
93
  end
94
94
 
95
- # Read and write data to cookies through ActionController::Base#cookies.
95
+ # Read and write data to cookies through ActionController::Cookies#cookies.
96
96
  #
97
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.
98
+ # When writing cookie data, the data is sent out in the HTTP response header, +Set-Cookie+.
99
99
  #
100
100
  # Examples of writing:
101
101
  #
@@ -160,13 +160,18 @@ module ActionDispatch
160
160
  # to <tt>:all</tt>. To support multiple domains, provide an array, and
161
161
  # the first domain matching <tt>request.host</tt> will be used. Make
162
162
  # sure to specify the <tt>:domain</tt> option with <tt>:all</tt> or
163
- # <tt>Array</tt> again when deleting cookies.
163
+ # <tt>Array</tt> again when deleting cookies. For more flexibility you
164
+ # can set the domain on a per-request basis by specifying <tt>:domain</tt>
165
+ # with a proc.
164
166
  #
165
167
  # domain: nil # Does not set cookie domain. (default)
166
168
  # domain: :all # Allow the cookie for the top most level
167
169
  # # domain and subdomains.
168
170
  # domain: %w(.example.com .example.org) # Allow the cookie
169
171
  # # for concrete domain names.
172
+ # domain: proc { Tenant.current.cookie_domain } # Set cookie domain dynamically
173
+ # domain: proc { |req| ".sub.#{req.host}" } # Set cookie domain dynamically based on request
174
+ #
170
175
  #
171
176
  # * <tt>:tld_length</tt> - When using <tt>:domain => :all</tt>, this option can be used to explicitly
172
177
  # set the TLD length when using a short (<= 3 character) domain that is being interpreted as part of a TLD.
@@ -178,7 +183,8 @@ module ActionDispatch
178
183
  # only HTTP. Defaults to +false+.
179
184
  # * <tt>:same_site</tt> - The value of the +SameSite+ cookie attribute, which
180
185
  # determines how this cookie should be restricted in cross-site contexts.
181
- # Possible values are +:none+, +:lax+, and +:strict+. Defaults to +:lax+.
186
+ # Possible values are +nil+, +:none+, +:lax+, and +:strict+. Defaults to
187
+ # +:lax+.
182
188
  class Cookies
183
189
  HTTP_HEADER = "Set-Cookie"
184
190
  GENERATOR_KEY = "action_dispatch.key_generator"
@@ -376,6 +382,8 @@ module ActionDispatch
376
382
  # Removes the cookie on the client machine by setting the value to an empty string
377
383
  # and the expiration date in the past. Like <tt>[]=</tt>, you can pass in
378
384
  # an options hash to delete cookies with extra data such as a <tt>:path</tt>.
385
+ #
386
+ # Returns the value of the cookie, or +nil+ if the cookie does not exist.
379
387
  def delete(name, options = {})
380
388
  return unless @cookies.has_key? name.to_s
381
389
 
@@ -401,9 +409,15 @@ module ActionDispatch
401
409
  @cookies.each_key { |k| delete(k, options) }
402
410
  end
403
411
 
404
- def write(headers)
405
- if header = make_set_cookie_header(headers[HTTP_HEADER])
406
- headers[HTTP_HEADER] = header
412
+ def write(response)
413
+ @set_cookies.each do |name, value|
414
+ if write_cookie?(value)
415
+ response.set_cookie(name, value)
416
+ end
417
+ end
418
+
419
+ @delete_cookies.each do |name, value|
420
+ response.delete_cookie(name, value)
407
421
  end
408
422
  end
409
423
 
@@ -414,19 +428,6 @@ module ActionDispatch
414
428
  ::Rack::Utils.escape(string)
415
429
  end
416
430
 
417
- def make_set_cookie_header(header)
418
- header = @set_cookies.inject(header) { |m, (k, v)|
419
- if write_cookie?(v)
420
- ::Rack::Utils.add_cookie_to_header(m, k, v)
421
- else
422
- m
423
- end
424
- }
425
- @delete_cookies.inject(header) { |m, (k, v)|
426
- ::Rack::Utils.add_remove_cookie_to_header(m, k, v)
427
- }
428
- end
429
-
430
431
  def write_cookie?(cookie)
431
432
  request.ssl? || !cookie[:secure] || always_write_cookie || request.host.end_with?(".onion")
432
433
  end
@@ -438,12 +439,13 @@ module ActionDispatch
438
439
 
439
440
  options[:path] ||= "/"
440
441
 
441
- cookies_same_site_protection = request.cookies_same_site_protection
442
- options[:same_site] ||= cookies_same_site_protection.call(request)
442
+ unless options.key?(:same_site)
443
+ options[:same_site] = request.cookies_same_site_protection
444
+ end
443
445
 
444
446
  if options[:domain] == :all || options[:domain] == "all"
445
447
  cookie_domain = ""
446
- dot_splitted_host = request.host.split('.', -1)
448
+ dot_splitted_host = request.host.split(".", -1)
447
449
 
448
450
  # Case where request.host is not an IP address or it's an invalid domain
449
451
  # (ip confirms to the domain structure we expect so we explicitly check for ip)
@@ -453,10 +455,10 @@ module ActionDispatch
453
455
  end
454
456
 
455
457
  # If there is a provided tld length then we use it otherwise default domain.
456
- if options[:tld_length].present?
458
+ if options[:tld_length].present?
457
459
  # Case where the tld_length provided is valid
458
460
  if dot_splitted_host.length >= options[:tld_length]
459
- cookie_domain = dot_splitted_host.last(options[:tld_length]).join('.')
461
+ cookie_domain = dot_splitted_host.last(options[:tld_length]).join(".")
460
462
  end
461
463
  # Case where tld_length is not provided
462
464
  else
@@ -465,12 +467,12 @@ module ActionDispatch
465
467
  cookie_domain = dot_splitted_host.last(2).join(".")
466
468
  # **.**, ***.** style TLDs like co.uk and com.au
467
469
  else
468
- cookie_domain = dot_splitted_host.last(3).join('.')
470
+ cookie_domain = dot_splitted_host.last(3).join(".")
469
471
  end
470
472
  end
471
473
 
472
474
  options[:domain] = if cookie_domain.present?
473
- ".#{cookie_domain}"
475
+ cookie_domain
474
476
  end
475
477
  elsif options[:domain].is_a? Array
476
478
  # If host matches one of the supplied domains.
@@ -478,6 +480,8 @@ module ActionDispatch
478
480
  domain = domain.delete_prefix(".")
479
481
  request.host == domain || request.host.end_with?(".#{domain}")
480
482
  end
483
+ elsif options[:domain].respond_to?(:call)
484
+ options[:domain] = options[:domain].call(request)
481
485
  end
482
486
  end
483
487
  end
@@ -541,75 +545,57 @@ module ActionDispatch
541
545
  end
542
546
  end
543
547
 
544
- class MarshalWithJsonFallback # :nodoc:
545
- def self.load(value)
546
- Marshal.load(value)
547
- rescue TypeError => e
548
- ActiveSupport::JSON.decode(value) rescue raise e
549
- end
550
-
551
- def self.dump(value)
552
- Marshal.dump(value)
553
- end
554
- end
555
-
556
- class JsonSerializer # :nodoc:
557
- def self.load(value)
558
- ActiveSupport::JSON.decode(value)
559
- end
560
-
561
- def self.dump(value)
562
- ActiveSupport::JSON.encode(value)
563
- end
564
- end
565
-
566
548
  module SerializedCookieJars # :nodoc:
567
- MARSHAL_SIGNATURE = "\x04\x08"
568
549
  SERIALIZER = ActiveSupport::MessageEncryptor::NullSerializer
569
550
 
570
551
  protected
571
- def needs_migration?(value)
572
- request.cookies_serializer == :hybrid && value.start_with?(MARSHAL_SIGNATURE)
552
+ def digest
553
+ request.cookies_digest || "SHA1"
573
554
  end
574
555
 
575
- def serialize(value)
576
- serializer.dump(value)
556
+ private
557
+ def serializer
558
+ @serializer ||=
559
+ case request.cookies_serializer
560
+ when nil
561
+ ActiveSupport::Messages::SerializerWithFallback[:marshal]
562
+ when :hybrid
563
+ ActiveSupport::Messages::SerializerWithFallback[:json_allow_marshal]
564
+ when Symbol
565
+ ActiveSupport::Messages::SerializerWithFallback[request.cookies_serializer]
566
+ else
567
+ request.cookies_serializer
568
+ end
577
569
  end
578
570
 
579
- def deserialize(name)
580
- rotate = false
581
- value = yield -> { rotate = true }
571
+ def reserialize?(dumped)
572
+ serializer.is_a?(ActiveSupport::Messages::SerializerWithFallback) &&
573
+ serializer != ActiveSupport::Messages::SerializerWithFallback[:marshal] &&
574
+ !serializer.dumped?(dumped)
575
+ end
582
576
 
583
- if value
584
- case
585
- when needs_migration?(value)
586
- Marshal.load(value).tap do |v|
587
- self[name] = { value: v }
588
- end
589
- when rotate
590
- serializer.load(value).tap do |v|
591
- self[name] = { value: v }
592
- end
593
- else
594
- serializer.load(value)
577
+ def parse(name, dumped, force_reserialize: false, **)
578
+ if dumped
579
+ begin
580
+ value = serializer.load(dumped)
581
+ rescue StandardError
582
+ return
595
583
  end
584
+
585
+ self[name] = { value: value } if force_reserialize || reserialize?(dumped)
586
+
587
+ value
596
588
  end
597
589
  end
598
590
 
599
- def serializer
600
- serializer = request.cookies_serializer || :marshal
601
- case serializer
602
- when :marshal
603
- MarshalWithJsonFallback
604
- when :json, :hybrid
605
- JsonSerializer
606
- else
607
- serializer
608
- end
591
+ def commit(name, options)
592
+ options[:value] = serializer.dump(options[:value])
609
593
  end
610
594
 
611
- def digest
612
- request.cookies_digest || "SHA1"
595
+ def check_for_overflow!(name, options)
596
+ if options[:value].bytesize > MAX_COOKIE_SIZE
597
+ raise CookieOverflow, "#{name} cookie overflowed with size #{options[:value].bytesize} bytes"
598
+ end
613
599
  end
614
600
  end
615
601
 
@@ -630,15 +616,15 @@ module ActionDispatch
630
616
 
631
617
  private
632
618
  def parse(name, signed_message, purpose: nil)
633
- deserialize(name) do |rotate|
634
- @verifier.verified(signed_message, on_rotation: rotate, purpose: purpose)
635
- end
619
+ rotated = false
620
+ data = @verifier.verified(signed_message, purpose: purpose, on_rotation: -> { rotated = true })
621
+ super(name, data, force_reserialize: rotated)
636
622
  end
637
623
 
638
624
  def commit(name, options)
639
- options[:value] = @verifier.generate(serialize(options[:value]), **cookie_metadata(name, options))
640
-
641
- raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
625
+ super
626
+ options[:value] = @verifier.generate(options[:value], **cookie_metadata(name, options))
627
+ check_for_overflow!(name, options)
642
628
  end
643
629
  end
644
630
 
@@ -680,17 +666,17 @@ module ActionDispatch
680
666
 
681
667
  private
682
668
  def parse(name, encrypted_message, purpose: nil)
683
- deserialize(name) do |rotate|
684
- @encryptor.decrypt_and_verify(encrypted_message, on_rotation: rotate, purpose: purpose)
685
- end
669
+ rotated = false
670
+ data = @encryptor.decrypt_and_verify(encrypted_message, purpose: purpose, on_rotation: -> { rotated = true })
671
+ super(name, data, force_reserialize: rotated)
686
672
  rescue ActiveSupport::MessageEncryptor::InvalidMessage, ActiveSupport::MessageVerifier::InvalidSignature
687
673
  nil
688
674
  end
689
675
 
690
676
  def commit(name, options)
691
- options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value]), **cookie_metadata(name, options))
692
-
693
- raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
677
+ super
678
+ options[:value] = @encryptor.encrypt_and_sign(options[:value], **cookie_metadata(name, options))
679
+ check_for_overflow!(name, options)
694
680
  end
695
681
  end
696
682
 
@@ -699,21 +685,18 @@ module ActionDispatch
699
685
  end
700
686
 
701
687
  def call(env)
702
- request = ActionDispatch::Request.new env
703
-
704
- status, headers, body = @app.call(env)
688
+ request = ActionDispatch::Request.new(env)
689
+ response = @app.call(env)
705
690
 
706
691
  if request.have_cookie_jar?
707
692
  cookie_jar = request.cookie_jar
708
693
  unless cookie_jar.committed?
709
- cookie_jar.write(headers)
710
- if headers[HTTP_HEADER].respond_to?(:join)
711
- headers[HTTP_HEADER] = headers[HTTP_HEADER].join("\n")
712
- end
694
+ response = Rack::Response[*response]
695
+ cookie_jar.write(response)
713
696
  end
714
697
  end
715
698
 
716
- [status, headers, body]
699
+ response.to_a
717
700
  end
718
701
  end
719
702
  end
@@ -6,6 +6,8 @@ require "action_dispatch/routing/inspector"
6
6
  require "action_view"
7
7
 
8
8
  module ActionDispatch
9
+ # = Action Dispatch \DebugExceptions
10
+ #
9
11
  # This middleware is responsible for logging exceptions and
10
12
  # showing a debugging page in case the request is local.
11
13
  class DebugExceptions
@@ -24,26 +26,26 @@ module ActionDispatch
24
26
  end
25
27
 
26
28
  def call(env)
27
- request = ActionDispatch::Request.new env
28
29
  _, headers, body = response = @app.call(env)
29
30
 
30
- if headers["X-Cascade"] == "pass"
31
+ if headers[Constants::X_CASCADE] == "pass"
31
32
  body.close if body.respond_to?(:close)
32
33
  raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
33
34
  end
34
35
 
35
36
  response
36
37
  rescue Exception => exception
37
- invoke_interceptors(request, exception)
38
- raise exception unless request.show_exceptions?
39
- render_exception(request, exception)
38
+ request = ActionDispatch::Request.new env
39
+ backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
40
+ wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
41
+
42
+ invoke_interceptors(request, exception, wrapper)
43
+ raise exception unless wrapper.show?(request)
44
+ render_exception(request, exception, wrapper)
40
45
  end
41
46
 
42
47
  private
43
- def invoke_interceptors(request, exception)
44
- backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
45
- wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
46
-
48
+ def invoke_interceptors(request, exception, wrapper)
47
49
  @interceptors.each do |interceptor|
48
50
  interceptor.call(request, exception)
49
51
  rescue Exception
@@ -51,9 +53,7 @@ module ActionDispatch
51
53
  end
52
54
  end
53
55
 
54
- def render_exception(request, exception)
55
- backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
56
- wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
56
+ def render_exception(request, exception, wrapper)
57
57
  log_error(request, wrapper)
58
58
 
59
59
  if request.get_header("action_dispatch.show_detailed_exceptions")
@@ -94,7 +94,7 @@ module ActionDispatch
94
94
  wrapper.status_code,
95
95
  Rack::Utils::HTTP_STATUS_CODES[500]
96
96
  ),
97
- exception: wrapper.exception.inspect,
97
+ exception: wrapper.exception_inspect,
98
98
  traces: wrapper.traces
99
99
  }
100
100
 
@@ -115,19 +115,19 @@ module ActionDispatch
115
115
  DebugView.new(
116
116
  request: request,
117
117
  exception_wrapper: wrapper,
118
+ # Everything should use the wrapper, but we need to pass
119
+ # `exception` for legacy code.
118
120
  exception: wrapper.exception,
119
121
  traces: wrapper.traces,
120
122
  show_source_idx: wrapper.source_to_show_id,
121
123
  trace_to_show: wrapper.trace_to_show,
122
- routes_inspector: routes_inspector(wrapper.exception),
124
+ routes_inspector: routes_inspector(wrapper),
123
125
  source_extracts: wrapper.source_extracts,
124
- line_number: wrapper.line_number,
125
- file: wrapper.file
126
126
  )
127
127
  end
128
128
 
129
129
  def render(status, body, format)
130
- [status, { "Content-Type" => "#{format}; charset=#{Response.default_charset}", "Content-Length" => body.bytesize.to_s }, [body]]
130
+ [status, { Rack::CONTENT_TYPE => "#{format}; charset=#{Response.default_charset}", Rack::CONTENT_LENGTH => body.bytesize.to_s }, [body]]
131
131
  end
132
132
 
133
133
  def log_error(request, wrapper)
@@ -136,26 +136,27 @@ module ActionDispatch
136
136
  return unless logger
137
137
  return if !log_rescued_responses?(request) && wrapper.rescue_response?
138
138
 
139
- exception = wrapper.exception
140
139
  trace = wrapper.exception_trace
141
140
 
142
141
  message = []
143
142
  message << " "
144
- message << "#{exception.class} (#{exception.message}):"
145
- message.concat(exception.annotated_source_code) if exception.respond_to?(:annotated_source_code)
143
+ message << "#{wrapper.exception_class_name} (#{wrapper.message}):"
144
+ message.concat(wrapper.annotated_source_code)
146
145
  message << " "
147
146
  message.concat(trace)
148
147
 
149
- log_array(logger, message)
148
+ log_array(logger, message, request)
150
149
  end
151
150
 
152
- def log_array(logger, lines)
151
+ def log_array(logger, lines, request)
153
152
  return if lines.empty?
154
153
 
154
+ level = request.get_header("action_dispatch.debug_exception_log_level")
155
+
155
156
  if logger.formatter && logger.formatter.respond_to?(:tags_text)
156
- logger.fatal lines.join("\n#{logger.formatter.tags_text}")
157
+ logger.add(level, lines.join("\n#{logger.formatter.tags_text}"))
157
158
  else
158
- logger.fatal lines.join("\n")
159
+ logger.add(level, lines.join("\n"))
159
160
  end
160
161
  end
161
162
 
@@ -168,7 +169,7 @@ module ActionDispatch
168
169
  end
169
170
 
170
171
  def routes_inspector(exception)
171
- if @routes_app.respond_to?(:routes) && (exception.is_a?(ActionController::RoutingError) || exception.is_a?(ActionView::Template::Error))
172
+ if @routes_app.respond_to?(:routes) && (exception.routing_error? || exception.template_error?)
172
173
  ActionDispatch::Routing::RoutesInspector.new(@routes_app.routes.routes)
173
174
  end
174
175
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionDispatch
4
+ # = Action Dispatch \DebugLocks
5
+ #
4
6
  # This middleware can be used to diagnose deadlocks in the autoload interlock.
5
7
  #
6
8
  # To use it, insert it near the top of the middleware stack, using
@@ -97,7 +99,8 @@ module ActionDispatch
97
99
  msg << "\n#{info[:backtrace].join("\n")}\n" if info[:backtrace]
98
100
  end.join("\n\n---\n\n\n")
99
101
 
100
- [200, { "Content-Type" => "text/plain", "Content-Length" => str.size }, [str]]
102
+ [200, { Rack::CONTENT_TYPE => "text/plain; charset=#{ActionDispatch::Response.default_charset}",
103
+ Rack::CONTENT_LENGTH => str.size.to_s }, [str]]
101
104
  end
102
105
 
103
106
  def blocked_by?(victim, blocker, all_threads)
@@ -7,18 +7,23 @@ require "action_view/base"
7
7
 
8
8
  module ActionDispatch
9
9
  class DebugView < ActionView::Base # :nodoc:
10
- RESCUES_TEMPLATE_PATH = File.expand_path("templates", __dir__)
10
+ RESCUES_TEMPLATE_PATHS = [File.expand_path("templates", __dir__)]
11
11
 
12
12
  def initialize(assigns)
13
- paths = [RESCUES_TEMPLATE_PATH]
13
+ paths = RESCUES_TEMPLATE_PATHS.dup
14
14
  lookup_context = ActionView::LookupContext.new(paths)
15
15
  super(lookup_context, assigns, nil)
16
+ @exception_wrapper = assigns[:exception_wrapper]
16
17
  end
17
18
 
18
19
  def compiled_method_container
19
20
  self.class
20
21
  end
21
22
 
23
+ def error_highlight_available?
24
+ @exception_wrapper.error_highlight_available?
25
+ end
26
+
22
27
  def debug_params(params)
23
28
  clean_params = params.clone
24
29
  clean_params.delete("action")