actionpack 7.0.8 → 7.1.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (136) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +360 -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 +7 -4
  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 +40 -18
  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