actionpack 7.0.8.1 → 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 (136) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +365 -353
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -2
  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 +61 -18
  10. data/lib/abstract_controller/railties/routes_helpers.rb +1 -16
  11. data/lib/abstract_controller/rendering.rb +3 -3
  12. data/lib/abstract_controller/translation.rb +1 -20
  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 +5 -3
  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/content_security_policy.rb +1 -1
  22. data/lib/action_controller/metal/data_streaming.rb +2 -0
  23. data/lib/action_controller/metal/default_headers.rb +2 -0
  24. data/lib/action_controller/metal/etag_with_flash.rb +2 -0
  25. data/lib/action_controller/metal/etag_with_template_digest.rb +2 -0
  26. data/lib/action_controller/metal/exceptions.rb +8 -0
  27. data/lib/action_controller/metal/head.rb +8 -6
  28. data/lib/action_controller/metal/helpers.rb +3 -14
  29. data/lib/action_controller/metal/http_authentication.rb +17 -8
  30. data/lib/action_controller/metal/implicit_render.rb +5 -3
  31. data/lib/action_controller/metal/instrumentation.rb +8 -1
  32. data/lib/action_controller/metal/live.rb +24 -0
  33. data/lib/action_controller/metal/mime_responds.rb +2 -2
  34. data/lib/action_controller/metal/params_wrapper.rb +4 -2
  35. data/lib/action_controller/metal/permissions_policy.rb +1 -1
  36. data/lib/action_controller/metal/redirecting.rb +7 -7
  37. data/lib/action_controller/metal/renderers.rb +2 -2
  38. data/lib/action_controller/metal/rendering.rb +0 -7
  39. data/lib/action_controller/metal/request_forgery_protection.rb +139 -50
  40. data/lib/action_controller/metal/rescue.rb +2 -0
  41. data/lib/action_controller/metal/streaming.rb +70 -30
  42. data/lib/action_controller/metal/strong_parameters.rb +132 -52
  43. data/lib/action_controller/metal/url_for.rb +7 -0
  44. data/lib/action_controller/metal.rb +79 -21
  45. data/lib/action_controller/railtie.rb +22 -9
  46. data/lib/action_controller/renderer.rb +98 -65
  47. data/lib/action_controller/test_case.rb +15 -5
  48. data/lib/action_controller.rb +8 -1
  49. data/lib/action_dispatch/constants.rb +32 -0
  50. data/lib/action_dispatch/deprecator.rb +7 -0
  51. data/lib/action_dispatch/http/cache.rb +1 -3
  52. data/lib/action_dispatch/http/content_security_policy.rb +9 -8
  53. data/lib/action_dispatch/http/filter_parameters.rb +11 -5
  54. data/lib/action_dispatch/http/headers.rb +2 -0
  55. data/lib/action_dispatch/http/mime_negotiation.rb +22 -22
  56. data/lib/action_dispatch/http/mime_type.rb +35 -12
  57. data/lib/action_dispatch/http/mime_types.rb +3 -1
  58. data/lib/action_dispatch/http/parameters.rb +1 -1
  59. data/lib/action_dispatch/http/permissions_policy.rb +38 -23
  60. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  61. data/lib/action_dispatch/http/request.rb +48 -14
  62. data/lib/action_dispatch/http/response.rb +80 -59
  63. data/lib/action_dispatch/http/upload.rb +2 -0
  64. data/lib/action_dispatch/journey/formatter.rb +8 -2
  65. data/lib/action_dispatch/journey/path/pattern.rb +14 -14
  66. data/lib/action_dispatch/journey/route.rb +3 -2
  67. data/lib/action_dispatch/journey/router.rb +9 -8
  68. data/lib/action_dispatch/journey/routes.rb +2 -2
  69. data/lib/action_dispatch/log_subscriber.rb +23 -0
  70. data/lib/action_dispatch/middleware/actionable_exceptions.rb +5 -6
  71. data/lib/action_dispatch/middleware/assume_ssl.rb +24 -0
  72. data/lib/action_dispatch/middleware/callbacks.rb +2 -0
  73. data/lib/action_dispatch/middleware/cookies.rb +81 -98
  74. data/lib/action_dispatch/middleware/debug_exceptions.rb +26 -25
  75. data/lib/action_dispatch/middleware/debug_locks.rb +4 -1
  76. data/lib/action_dispatch/middleware/debug_view.rb +7 -2
  77. data/lib/action_dispatch/middleware/exception_wrapper.rb +186 -27
  78. data/lib/action_dispatch/middleware/executor.rb +1 -1
  79. data/lib/action_dispatch/middleware/flash.rb +7 -0
  80. data/lib/action_dispatch/middleware/host_authorization.rb +6 -3
  81. data/lib/action_dispatch/middleware/public_exceptions.rb +5 -3
  82. data/lib/action_dispatch/middleware/reloader.rb +7 -5
  83. data/lib/action_dispatch/middleware/remote_ip.rb +17 -16
  84. data/lib/action_dispatch/middleware/request_id.rb +2 -0
  85. data/lib/action_dispatch/middleware/server_timing.rb +4 -4
  86. data/lib/action_dispatch/middleware/session/abstract_store.rb +5 -0
  87. data/lib/action_dispatch/middleware/session/cache_store.rb +2 -0
  88. data/lib/action_dispatch/middleware/session/cookie_store.rb +11 -5
  89. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +3 -1
  90. data/lib/action_dispatch/middleware/show_exceptions.rb +19 -15
  91. data/lib/action_dispatch/middleware/ssl.rb +18 -6
  92. data/lib/action_dispatch/middleware/stack.rb +7 -2
  93. data/lib/action_dispatch/middleware/static.rb +12 -8
  94. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
  95. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +4 -4
  96. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
  97. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +7 -7
  98. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
  99. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +17 -0
  100. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +16 -12
  101. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -1
  102. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
  103. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +4 -4
  104. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  105. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  106. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +3 -0
  107. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +46 -37
  108. data/lib/action_dispatch/railtie.rb +14 -4
  109. data/lib/action_dispatch/request/session.rb +16 -6
  110. data/lib/action_dispatch/request/utils.rb +8 -3
  111. data/lib/action_dispatch/routing/inspector.rb +54 -6
  112. data/lib/action_dispatch/routing/mapper.rb +35 -24
  113. data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -0
  114. data/lib/action_dispatch/routing/redirection.rb +15 -6
  115. data/lib/action_dispatch/routing/route_set.rb +52 -22
  116. data/lib/action_dispatch/routing/routes_proxy.rb +10 -15
  117. data/lib/action_dispatch/routing/url_for.rb +5 -1
  118. data/lib/action_dispatch/routing.rb +7 -7
  119. data/lib/action_dispatch/system_test_case.rb +3 -3
  120. data/lib/action_dispatch/system_testing/browser.rb +20 -19
  121. data/lib/action_dispatch/system_testing/driver.rb +13 -21
  122. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +27 -16
  123. data/lib/action_dispatch/testing/assertion_response.rb +1 -1
  124. data/lib/action_dispatch/testing/assertions/response.rb +13 -6
  125. data/lib/action_dispatch/testing/assertions/routing.rb +67 -28
  126. data/lib/action_dispatch/testing/assertions.rb +3 -1
  127. data/lib/action_dispatch/testing/integration.rb +27 -17
  128. data/lib/action_dispatch/testing/request_encoder.rb +4 -1
  129. data/lib/action_dispatch/testing/test_process.rb +4 -3
  130. data/lib/action_dispatch/testing/test_request.rb +1 -1
  131. data/lib/action_dispatch/testing/test_response.rb +23 -9
  132. data/lib/action_dispatch.rb +37 -4
  133. data/lib/action_pack/gem_version.rb +4 -4
  134. data/lib/action_pack/version.rb +1 -1
  135. data/lib/action_pack.rb +1 -1
  136. metadata +64 -28
@@ -57,6 +57,9 @@ module ActionDispatch
57
57
  end
58
58
 
59
59
  def generate(name, options, path_parameters)
60
+ original_options = options.dup
61
+ path_params = options.delete(:path_params) || {}
62
+ options = path_params.merge(options)
60
63
  constraints = path_parameters.merge(options)
61
64
  missing_keys = nil
62
65
 
@@ -70,8 +73,11 @@ module ActionDispatch
70
73
 
71
74
  missing_keys = missing_keys(route, parameterized_parts)
72
75
  next if missing_keys && !missing_keys.empty?
73
- params = options.dup.delete_if do |key, _|
74
- parameterized_parts.key?(key) || route.defaults.key?(key)
76
+ params = options.delete_if do |key, _|
77
+ # top-level params' normal behavior of generating query_params
78
+ # should be preserved even if the same key is also a bind_param
79
+ parameterized_parts.key?(key) || route.defaults.key?(key) ||
80
+ (path_params.key?(key) && !original_options.key?(key))
75
81
  end
76
82
 
77
83
  defaults = route.defaults
@@ -183,22 +183,22 @@ module ActionDispatch
183
183
  end
184
184
 
185
185
  def offsets
186
- return @offsets if @offsets
187
-
188
- @offsets = [0]
189
-
190
- spec.find_all(&:symbol?).each do |node|
191
- node = node.to_sym
192
-
193
- if @requirements.key?(node)
194
- re = /#{Regexp.union(@requirements[node])}|/
195
- @offsets.push((re.match("").length - 1) + @offsets.last)
196
- else
197
- @offsets << @offsets.last
186
+ @offsets ||= begin
187
+ offsets = [0]
188
+
189
+ spec.find_all(&:symbol?).each do |node|
190
+ node = node.to_sym
191
+
192
+ if @requirements.key?(node)
193
+ re = /#{Regexp.union(@requirements[node])}|/
194
+ offsets.push((re.match("").length - 1) + offsets.last)
195
+ else
196
+ offsets << offsets.last
197
+ end
198
198
  end
199
- end
200
199
 
201
- @offsets
200
+ offsets
201
+ end
202
202
  end
203
203
  end
204
204
  end
@@ -5,7 +5,7 @@ module ActionDispatch
5
5
  module Journey
6
6
  class Route
7
7
  attr_reader :app, :path, :defaults, :name, :precedence, :constraints,
8
- :internal, :scope_options, :ast
8
+ :internal, :scope_options, :ast, :source_location
9
9
 
10
10
  alias :conditions :constraints
11
11
 
@@ -53,7 +53,7 @@ module ActionDispatch
53
53
  ##
54
54
  # +path+ is a path constraint.
55
55
  # +constraints+ is a hash of constraints to be applied to this route.
56
- def initialize(name:, app: nil, path:, constraints: {}, required_defaults: [], defaults: {}, request_method_match: nil, precedence: 0, scope_options: {}, internal: false)
56
+ def initialize(name:, app: nil, path:, constraints: {}, required_defaults: [], defaults: {}, request_method_match: nil, precedence: 0, scope_options: {}, internal: false, source_location: nil)
57
57
  @name = name
58
58
  @app = app
59
59
  @path = path
@@ -69,6 +69,7 @@ module ActionDispatch
69
69
  @path_formatter = @path.build_formatter
70
70
  @scope_options = scope_options
71
71
  @internal = internal
72
+ @source_location = source_location
72
73
 
73
74
  @ast = @path.ast.root
74
75
  @path.ast.route = self
@@ -29,7 +29,7 @@ module ActionDispatch
29
29
  end
30
30
 
31
31
  def serve(req)
32
- find_routes(req).each do |match, parameters, route|
32
+ find_routes(req) do |match, parameters, route|
33
33
  set_params = req.path_parameters
34
34
  path_info = req.path_info
35
35
  script_name = req.script_name
@@ -46,24 +46,25 @@ module ActionDispatch
46
46
  }
47
47
 
48
48
  req.path_parameters = tmp_params
49
+ req.route_uri_pattern = route.path.spec.to_s
49
50
 
50
- status, headers, body = route.app.serve(req)
51
+ _, headers, _ = response = route.app.serve(req)
51
52
 
52
- if "pass" == headers["X-Cascade"]
53
+ if "pass" == headers[Constants::X_CASCADE]
53
54
  req.script_name = script_name
54
55
  req.path_info = path_info
55
56
  req.path_parameters = set_params
56
57
  next
57
58
  end
58
59
 
59
- return [status, headers, body]
60
+ return response
60
61
  end
61
62
 
62
- [404, { "X-Cascade" => "pass" }, ["Not Found"]]
63
+ [404, { Constants::X_CASCADE => "pass" }, ["Not Found"]]
63
64
  end
64
65
 
65
66
  def recognize(rails_req)
66
- find_routes(rails_req).each do |match, parameters, route|
67
+ find_routes(rails_req) do |match, parameters, route|
67
68
  unless route.path.anchored
68
69
  rails_req.script_name = match.to_s
69
70
  rails_req.path_info = match.post_match
@@ -120,14 +121,14 @@ module ActionDispatch
120
121
 
121
122
  routes.sort_by!(&:precedence)
122
123
 
123
- routes.map! { |r|
124
+ routes.each { |r|
124
125
  match_data = r.path.match(path_info)
125
126
  path_parameters = {}
126
127
  match_data.names.each_with_index { |name, i|
127
128
  val = match_data[i + 1]
128
129
  path_parameters[name.to_sym] = Utils.unescape_uri(val) if val
129
130
  }
130
- [match_data, path_parameters, r]
131
+ yield [match_data, path_parameters, r]
131
132
  }
132
133
  end
133
134
 
@@ -9,8 +9,8 @@ module ActionDispatch
9
9
 
10
10
  attr_reader :routes, :custom_routes, :anchored_routes
11
11
 
12
- def initialize
13
- @routes = []
12
+ def initialize(routes = [])
13
+ @routes = routes
14
14
  @ast = nil
15
15
  @anchored_routes = []
16
16
  @custom_routes = []
@@ -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,7 +92,7 @@ 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
98
  # When writing cookie data, the data is sent out in the HTTP response header, +Set-Cookie+.
@@ -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,8 +439,9 @@ 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 = ""
@@ -470,7 +472,7 @@ module ActionDispatch
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
686
- rescue ActiveSupport::MessageEncryptor::InvalidMessage, ActiveSupport::MessageVerifier::InvalidSignature, JSON::ParserError
669
+ rotated = false
670
+ data = @encryptor.decrypt_and_verify(encrypted_message, purpose: purpose, on_rotation: -> { rotated = true })
671
+ super(name, data, force_reserialize: rotated)
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