actionpack 7.0.4 → 7.1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +495 -257
  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 +11 -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 +15 -9
  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 +29 -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 +200 -103
  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 +18 -8
  50. data/lib/action_controller.rb +13 -3
  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 +9 -11
  54. data/lib/action_dispatch/http/content_security_policy.rb +35 -13
  55. data/lib/action_dispatch/http/filter_parameters.rb +23 -32
  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 +37 -11
  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 +85 -32
  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 +108 -117
  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 +7 -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 +39 -22
  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 +13 -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 +97 -26
  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 +53 -23
  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 +25 -19
  125. data/lib/action_dispatch/system_testing/driver.rb +15 -23
  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 +41 -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 +68 -32
@@ -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,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"
@@ -290,20 +296,6 @@ module ActionDispatch
290
296
  class CookieJar # :nodoc:
291
297
  include Enumerable, ChainedCookieJars
292
298
 
293
- # This regular expression is used to split the levels of a domain.
294
- # The top level domain can be any string without a period or
295
- # **.**, ***.** style TLDs like co.uk or com.au
296
- #
297
- # www.example.co.uk gives:
298
- # $& => example.co.uk
299
- #
300
- # example.com gives:
301
- # $& => example.com
302
- #
303
- # lots.of.subdomains.example.local gives:
304
- # $& => example.local
305
- DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/
306
-
307
299
  def self.build(req, cookies)
308
300
  jar = new(req)
309
301
  jar.update(cookies)
@@ -390,6 +382,8 @@ module ActionDispatch
390
382
  # Removes the cookie on the client machine by setting the value to an empty string
391
383
  # and the expiration date in the past. Like <tt>[]=</tt>, you can pass in
392
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.
393
387
  def delete(name, options = {})
394
388
  return unless @cookies.has_key? name.to_s
395
389
 
@@ -415,9 +409,15 @@ module ActionDispatch
415
409
  @cookies.each_key { |k| delete(k, options) }
416
410
  end
417
411
 
418
- def write(headers)
419
- if header = make_set_cookie_header(headers[HTTP_HEADER])
420
- 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)
421
421
  end
422
422
  end
423
423
 
@@ -428,19 +428,6 @@ module ActionDispatch
428
428
  ::Rack::Utils.escape(string)
429
429
  end
430
430
 
431
- def make_set_cookie_header(header)
432
- header = @set_cookies.inject(header) { |m, (k, v)|
433
- if write_cookie?(v)
434
- ::Rack::Utils.add_cookie_to_header(m, k, v)
435
- else
436
- m
437
- end
438
- }
439
- @delete_cookies.inject(header) { |m, (k, v)|
440
- ::Rack::Utils.add_remove_cookie_to_header(m, k, v)
441
- }
442
- end
443
-
444
431
  def write_cookie?(cookie)
445
432
  request.ssl? || !cookie[:secure] || always_write_cookie || request.host.end_with?(".onion")
446
433
  end
@@ -452,17 +439,40 @@ module ActionDispatch
452
439
 
453
440
  options[:path] ||= "/"
454
441
 
455
- cookies_same_site_protection = request.cookies_same_site_protection
456
- 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
457
445
 
458
446
  if options[:domain] == :all || options[:domain] == "all"
459
- # If there is a provided tld length then we use it otherwise default domain regexp.
460
- domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP
447
+ cookie_domain = ""
448
+ dot_splitted_host = request.host.split(".", -1)
449
+
450
+ # Case where request.host is not an IP address or it's an invalid domain
451
+ # (ip confirms to the domain structure we expect so we explicitly check for ip)
452
+ if request.host.match?(/^[\d.]+$/) || dot_splitted_host.include?("") || dot_splitted_host.length == 1
453
+ options[:domain] = nil
454
+ return
455
+ end
461
456
 
462
- # If host is not ip and matches domain regexp.
463
- # (ip confirms to domain regexp so we explicitly check for ip)
464
- options[:domain] = if !request.host.match?(/^[\d.]+$/) && (request.host =~ domain_regexp)
465
- ".#{$&}"
457
+ # If there is a provided tld length then we use it otherwise default domain.
458
+ if options[:tld_length].present?
459
+ # Case where the tld_length provided is valid
460
+ if dot_splitted_host.length >= options[:tld_length]
461
+ cookie_domain = dot_splitted_host.last(options[:tld_length]).join(".")
462
+ end
463
+ # Case where tld_length is not provided
464
+ else
465
+ # Regular TLDs
466
+ if !(/\.[^.]{2,3}\.[^.]{2}\z/.match?(request.host))
467
+ cookie_domain = dot_splitted_host.last(2).join(".")
468
+ # **.**, ***.** style TLDs like co.uk and com.au
469
+ else
470
+ cookie_domain = dot_splitted_host.last(3).join(".")
471
+ end
472
+ end
473
+
474
+ options[:domain] = if cookie_domain.present?
475
+ cookie_domain
466
476
  end
467
477
  elsif options[:domain].is_a? Array
468
478
  # If host matches one of the supplied domains.
@@ -470,6 +480,8 @@ module ActionDispatch
470
480
  domain = domain.delete_prefix(".")
471
481
  request.host == domain || request.host.end_with?(".#{domain}")
472
482
  end
483
+ elsif options[:domain].respond_to?(:call)
484
+ options[:domain] = options[:domain].call(request)
473
485
  end
474
486
  end
475
487
  end
@@ -533,75 +545,57 @@ module ActionDispatch
533
545
  end
534
546
  end
535
547
 
536
- class MarshalWithJsonFallback # :nodoc:
537
- def self.load(value)
538
- Marshal.load(value)
539
- rescue TypeError => e
540
- ActiveSupport::JSON.decode(value) rescue raise e
541
- end
542
-
543
- def self.dump(value)
544
- Marshal.dump(value)
545
- end
546
- end
547
-
548
- class JsonSerializer # :nodoc:
549
- def self.load(value)
550
- ActiveSupport::JSON.decode(value)
551
- end
552
-
553
- def self.dump(value)
554
- ActiveSupport::JSON.encode(value)
555
- end
556
- end
557
-
558
548
  module SerializedCookieJars # :nodoc:
559
- MARSHAL_SIGNATURE = "\x04\x08"
560
549
  SERIALIZER = ActiveSupport::MessageEncryptor::NullSerializer
561
550
 
562
551
  protected
563
- def needs_migration?(value)
564
- request.cookies_serializer == :hybrid && value.start_with?(MARSHAL_SIGNATURE)
552
+ def digest
553
+ request.cookies_digest || "SHA1"
565
554
  end
566
555
 
567
- def serialize(value)
568
- 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
569
569
  end
570
570
 
571
- def deserialize(name)
572
- rotate = false
573
- 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
574
576
 
575
- if value
576
- case
577
- when needs_migration?(value)
578
- Marshal.load(value).tap do |v|
579
- self[name] = { value: v }
580
- end
581
- when rotate
582
- serializer.load(value).tap do |v|
583
- self[name] = { value: v }
584
- end
585
- else
586
- 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
587
583
  end
584
+
585
+ self[name] = { value: value } if force_reserialize || reserialize?(dumped)
586
+
587
+ value
588
588
  end
589
589
  end
590
590
 
591
- def serializer
592
- serializer = request.cookies_serializer || :marshal
593
- case serializer
594
- when :marshal
595
- MarshalWithJsonFallback
596
- when :json, :hybrid
597
- JsonSerializer
598
- else
599
- serializer
600
- end
591
+ def commit(name, options)
592
+ options[:value] = serializer.dump(options[:value])
601
593
  end
602
594
 
603
- def digest
604
- 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
605
599
  end
606
600
  end
607
601
 
@@ -622,15 +616,15 @@ module ActionDispatch
622
616
 
623
617
  private
624
618
  def parse(name, signed_message, purpose: nil)
625
- deserialize(name) do |rotate|
626
- @verifier.verified(signed_message, on_rotation: rotate, purpose: purpose)
627
- end
619
+ rotated = false
620
+ data = @verifier.verified(signed_message, purpose: purpose, on_rotation: -> { rotated = true })
621
+ super(name, data, force_reserialize: rotated)
628
622
  end
629
623
 
630
624
  def commit(name, options)
631
- options[:value] = @verifier.generate(serialize(options[:value]), **cookie_metadata(name, options))
632
-
633
- 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)
634
628
  end
635
629
  end
636
630
 
@@ -672,17 +666,17 @@ module ActionDispatch
672
666
 
673
667
  private
674
668
  def parse(name, encrypted_message, purpose: nil)
675
- deserialize(name) do |rotate|
676
- @encryptor.decrypt_and_verify(encrypted_message, on_rotation: rotate, purpose: purpose)
677
- 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)
678
672
  rescue ActiveSupport::MessageEncryptor::InvalidMessage, ActiveSupport::MessageVerifier::InvalidSignature
679
673
  nil
680
674
  end
681
675
 
682
676
  def commit(name, options)
683
- options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value]), **cookie_metadata(name, options))
684
-
685
- 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)
686
680
  end
687
681
  end
688
682
 
@@ -691,21 +685,18 @@ module ActionDispatch
691
685
  end
692
686
 
693
687
  def call(env)
694
- request = ActionDispatch::Request.new env
695
-
696
- status, headers, body = @app.call(env)
688
+ request = ActionDispatch::Request.new(env)
689
+ response = @app.call(env)
697
690
 
698
691
  if request.have_cookie_jar?
699
692
  cookie_jar = request.cookie_jar
700
693
  unless cookie_jar.committed?
701
- cookie_jar.write(headers)
702
- if headers[HTTP_HEADER].respond_to?(:join)
703
- headers[HTTP_HEADER] = headers[HTTP_HEADER].join("\n")
704
- end
694
+ response = Rack::Response[*response]
695
+ cookie_jar.write(response)
705
696
  end
706
697
  end
707
698
 
708
- [status, headers, body]
699
+ response.to_a
709
700
  end
710
701
  end
711
702
  end