actionpack 6.1.7.5 → 7.1.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (160) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +355 -435
  3. data/MIT-LICENSE +2 -1
  4. data/README.rdoc +6 -7
  5. data/lib/abstract_controller/asset_paths.rb +1 -1
  6. data/lib/abstract_controller/base.rb +33 -37
  7. data/lib/abstract_controller/caching/fragments.rb +4 -2
  8. data/lib/abstract_controller/caching.rb +1 -1
  9. data/lib/abstract_controller/callbacks.rb +50 -11
  10. data/lib/abstract_controller/collector.rb +2 -2
  11. data/lib/abstract_controller/deprecator.rb +7 -0
  12. data/lib/abstract_controller/error.rb +1 -1
  13. data/lib/abstract_controller/helpers.rb +78 -30
  14. data/lib/abstract_controller/logger.rb +1 -1
  15. data/lib/abstract_controller/railties/routes_helpers.rb +3 -16
  16. data/lib/abstract_controller/rendering.rb +12 -14
  17. data/lib/abstract_controller/translation.rb +26 -7
  18. data/lib/abstract_controller/url_for.rb +6 -6
  19. data/lib/abstract_controller.rb +6 -0
  20. data/lib/action_controller/api.rb +12 -10
  21. data/lib/action_controller/base.rb +8 -21
  22. data/lib/action_controller/caching.rb +2 -0
  23. data/lib/action_controller/deprecator.rb +7 -0
  24. data/lib/action_controller/form_builder.rb +4 -2
  25. data/lib/action_controller/log_subscriber.rb +20 -7
  26. data/lib/action_controller/metal/basic_implicit_render.rb +3 -1
  27. data/lib/action_controller/metal/conditional_get.rb +137 -102
  28. data/lib/action_controller/metal/content_security_policy.rb +37 -3
  29. data/lib/action_controller/metal/cookies.rb +1 -1
  30. data/lib/action_controller/metal/data_streaming.rb +25 -31
  31. data/lib/action_controller/metal/default_headers.rb +2 -0
  32. data/lib/action_controller/metal/etag_with_flash.rb +3 -1
  33. data/lib/action_controller/metal/etag_with_template_digest.rb +2 -0
  34. data/lib/action_controller/metal/exceptions.rb +27 -30
  35. data/lib/action_controller/metal/flash.rb +6 -2
  36. data/lib/action_controller/metal/head.rb +9 -7
  37. data/lib/action_controller/metal/helpers.rb +5 -16
  38. data/lib/action_controller/metal/http_authentication.rb +78 -42
  39. data/lib/action_controller/metal/implicit_render.rb +5 -3
  40. data/lib/action_controller/metal/instrumentation.rb +62 -50
  41. data/lib/action_controller/metal/live.rb +67 -2
  42. data/lib/action_controller/metal/mime_responds.rb +5 -5
  43. data/lib/action_controller/metal/params_wrapper.rb +24 -13
  44. data/lib/action_controller/metal/permissions_policy.rb +20 -29
  45. data/lib/action_controller/metal/redirecting.rb +96 -23
  46. data/lib/action_controller/metal/renderers.rb +14 -15
  47. data/lib/action_controller/metal/rendering.rb +121 -16
  48. data/lib/action_controller/metal/request_forgery_protection.rb +208 -68
  49. data/lib/action_controller/metal/rescue.rb +7 -4
  50. data/lib/action_controller/metal/streaming.rb +74 -36
  51. data/lib/action_controller/metal/strong_parameters.rb +254 -151
  52. data/lib/action_controller/metal/testing.rb +9 -2
  53. data/lib/action_controller/metal/url_for.rb +10 -5
  54. data/lib/action_controller/metal.rb +89 -34
  55. data/lib/action_controller/railtie.rb +66 -9
  56. data/lib/action_controller/renderer.rb +99 -85
  57. data/lib/action_controller/test_case.rb +42 -11
  58. data/lib/action_controller.rb +10 -6
  59. data/lib/action_dispatch/constants.rb +32 -0
  60. data/lib/action_dispatch/deprecator.rb +7 -0
  61. data/lib/action_dispatch/http/cache.rb +21 -16
  62. data/lib/action_dispatch/http/content_security_policy.rb +122 -44
  63. data/lib/action_dispatch/http/filter_parameters.rb +14 -23
  64. data/lib/action_dispatch/http/headers.rb +3 -1
  65. data/lib/action_dispatch/http/mime_negotiation.rb +25 -15
  66. data/lib/action_dispatch/http/mime_type.rb +43 -22
  67. data/lib/action_dispatch/http/mime_types.rb +3 -1
  68. data/lib/action_dispatch/http/parameters.rb +6 -6
  69. data/lib/action_dispatch/http/permissions_policy.rb +57 -19
  70. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  71. data/lib/action_dispatch/http/request.rb +75 -51
  72. data/lib/action_dispatch/http/response.rb +81 -77
  73. data/lib/action_dispatch/http/upload.rb +15 -2
  74. data/lib/action_dispatch/http/url.rb +11 -19
  75. data/lib/action_dispatch/journey/formatter.rb +8 -2
  76. data/lib/action_dispatch/journey/gtg/builder.rb +11 -12
  77. data/lib/action_dispatch/journey/gtg/simulator.rb +10 -4
  78. data/lib/action_dispatch/journey/gtg/transition_table.rb +77 -21
  79. data/lib/action_dispatch/journey/nodes/node.rb +70 -5
  80. data/lib/action_dispatch/journey/path/pattern.rb +36 -27
  81. data/lib/action_dispatch/journey/route.rb +8 -14
  82. data/lib/action_dispatch/journey/router/utils.rb +2 -2
  83. data/lib/action_dispatch/journey/router.rb +10 -9
  84. data/lib/action_dispatch/journey/routes.rb +5 -5
  85. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  86. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  87. data/lib/action_dispatch/log_subscriber.rb +23 -0
  88. data/lib/action_dispatch/middleware/actionable_exceptions.rb +5 -7
  89. data/lib/action_dispatch/middleware/assume_ssl.rb +24 -0
  90. data/lib/action_dispatch/middleware/callbacks.rb +2 -0
  91. data/lib/action_dispatch/middleware/cookies.rb +97 -107
  92. data/lib/action_dispatch/middleware/debug_exceptions.rb +31 -28
  93. data/lib/action_dispatch/middleware/debug_locks.rb +7 -4
  94. data/lib/action_dispatch/middleware/debug_view.rb +7 -2
  95. data/lib/action_dispatch/middleware/exception_wrapper.rb +190 -27
  96. data/lib/action_dispatch/middleware/executor.rb +3 -0
  97. data/lib/action_dispatch/middleware/flash.rb +24 -18
  98. data/lib/action_dispatch/middleware/host_authorization.rb +19 -20
  99. data/lib/action_dispatch/middleware/public_exceptions.rb +5 -3
  100. data/lib/action_dispatch/middleware/reloader.rb +7 -5
  101. data/lib/action_dispatch/middleware/remote_ip.rb +32 -19
  102. data/lib/action_dispatch/middleware/request_id.rb +5 -3
  103. data/lib/action_dispatch/middleware/server_timing.rb +76 -0
  104. data/lib/action_dispatch/middleware/session/abstract_store.rb +6 -1
  105. data/lib/action_dispatch/middleware/session/cache_store.rb +2 -0
  106. data/lib/action_dispatch/middleware/session/cookie_store.rb +19 -13
  107. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +3 -1
  108. data/lib/action_dispatch/middleware/show_exceptions.rb +30 -25
  109. data/lib/action_dispatch/middleware/ssl.rb +18 -6
  110. data/lib/action_dispatch/middleware/stack.rb +34 -11
  111. data/lib/action_dispatch/middleware/static.rb +16 -16
  112. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
  113. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +5 -5
  114. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +4 -11
  115. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
  116. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +2 -2
  117. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +10 -5
  118. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +7 -3
  119. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +9 -9
  120. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
  121. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +3 -3
  122. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +45 -18
  123. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -15
  124. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +4 -4
  125. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +6 -6
  126. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +7 -7
  127. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
  128. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  129. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +3 -0
  130. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +64 -55
  131. data/lib/action_dispatch/railtie.rb +20 -4
  132. data/lib/action_dispatch/request/session.rb +59 -19
  133. data/lib/action_dispatch/request/utils.rb +8 -3
  134. data/lib/action_dispatch/routing/inspector.rb +55 -7
  135. data/lib/action_dispatch/routing/mapper.rb +117 -107
  136. data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -0
  137. data/lib/action_dispatch/routing/redirection.rb +20 -8
  138. data/lib/action_dispatch/routing/route_set.rb +67 -27
  139. data/lib/action_dispatch/routing/routes_proxy.rb +11 -16
  140. data/lib/action_dispatch/routing/url_for.rb +29 -26
  141. data/lib/action_dispatch/routing.rb +12 -13
  142. data/lib/action_dispatch/system_test_case.rb +8 -8
  143. data/lib/action_dispatch/system_testing/browser.rb +20 -29
  144. data/lib/action_dispatch/system_testing/driver.rb +34 -18
  145. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +35 -20
  146. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +0 -8
  147. data/lib/action_dispatch/testing/assertion_response.rb +1 -1
  148. data/lib/action_dispatch/testing/assertions/response.rb +14 -7
  149. data/lib/action_dispatch/testing/assertions/routing.rb +70 -30
  150. data/lib/action_dispatch/testing/assertions.rb +3 -4
  151. data/lib/action_dispatch/testing/integration.rb +33 -25
  152. data/lib/action_dispatch/testing/request_encoder.rb +4 -1
  153. data/lib/action_dispatch/testing/test_process.rb +5 -30
  154. data/lib/action_dispatch/testing/test_request.rb +1 -1
  155. data/lib/action_dispatch/testing/test_response.rb +34 -2
  156. data/lib/action_dispatch.rb +38 -4
  157. data/lib/action_pack/gem_version.rb +4 -4
  158. data/lib/action_pack/version.rb +1 -1
  159. data/lib/action_pack.rb +1 -1
  160. metadata +67 -30
@@ -68,7 +68,7 @@ function highlight_state(index, color) {
68
68
  }
69
69
 
70
70
  function highlight_finish(index) {
71
- svg_nodes[index].select('polygon')
71
+ svg_nodes[index].select('ellipse')
72
72
  .style("fill", "while")
73
73
  .transition().duration(500)
74
74
  .style("fill", "blue");
@@ -76,54 +76,79 @@ function highlight_finish(index) {
76
76
 
77
77
  function match(input) {
78
78
  reset_graph();
79
- var table = tt();
80
- var states = [0];
81
- var regexp_states = table['regexp_states'];
82
- var string_states = table['string_states'];
83
- var accepting = table['accepting'];
79
+ var table = tt();
80
+ var states = [[0, null]];
81
+ var regexp_states = table['regexp_states'];
82
+ var string_states = table['string_states'];
83
+ var stdparam_states = table['stdparam_states'];
84
+ var accepting = table['accepting'];
85
+ var default_re = new RegExp("^[^.\/?]+$");
86
+ var start_index = 0;
84
87
 
85
88
  highlight_state(0);
86
89
 
87
90
  tokenize(input, function(token) {
91
+ var end_index = start_index + token.length;
92
+
88
93
  var new_states = [];
89
94
  for(var key in states) {
90
- var state = states[key];
95
+ var state_parts = states[key];
96
+ var state = state_parts[0];
97
+ var previous_start = state_parts[1];
98
+
99
+ if(previous_start == null) {
100
+ if(string_states[state] && string_states[state][token]) {
101
+ var new_state = string_states[state][token];
102
+ highlight_edge(state, new_state);
103
+ highlight_state(new_state);
104
+ new_states.push([new_state, null]);
105
+ }
91
106
 
92
- if(string_states[state] && string_states[state][token]) {
93
- var new_state = string_states[state][token];
94
- highlight_edge(state, new_state);
95
- highlight_state(new_state);
96
- new_states.push(new_state);
107
+ if(stdparam_states[state] && default_re.test(token)) {
108
+ for(var key in stdparam_states[state]) {
109
+ var new_state = stdparam_states[state][key];
110
+ highlight_edge(state, new_state);
111
+ highlight_state(new_state);
112
+ new_states.push([new_state, null]);
113
+ }
114
+ }
97
115
  }
98
116
 
99
117
  if(regexp_states[state]) {
118
+ var slice_start = previous_start != null ? previous_start : start_index;
119
+
100
120
  for(var key in regexp_states[state]) {
101
121
  var re = new RegExp("^" + key + "$");
102
- if(re.test(token)) {
122
+
123
+ var accumulation = input.slice(slice_start, end_index);
124
+
125
+ if(re.test(accumulation)) {
103
126
  var new_state = regexp_states[state][key];
104
127
  highlight_edge(state, new_state);
105
128
  highlight_state(new_state);
106
- new_states.push(new_state);
129
+ new_states.push([new_state, null]);
107
130
  }
131
+
132
+ // retry the same regexp with the accumulated data either way
133
+ new_states.push([state, slice_start]);
108
134
  }
109
135
  }
110
136
  }
111
137
 
112
- if(new_states.length == 0) {
113
- return;
114
- }
115
138
  states = new_states;
139
+ start_index = end_index;
116
140
  });
117
141
 
118
142
  for(var key in states) {
119
- var state = states[key];
143
+ var state_parts = states[key];
144
+ var state = state_parts[0];
145
+ var slice_start = state_parts[1];
146
+
147
+ // we must ignore ones that are still accepting more data
148
+ if (slice_start != null) continue;
149
+
120
150
  if(accepting[state]) {
121
- for(var mkey in svg_edges[state]) {
122
- if(!regexp_states[mkey] && !string_states[mkey]) {
123
- highlight_edge(state, mkey);
124
- highlight_finish(mkey);
125
- }
126
- }
151
+ highlight_finish(state);
127
152
  } else {
128
153
  highlight_state(state, "red");
129
154
  }
@@ -8,7 +8,7 @@
8
8
  <%= style %>
9
9
  <% end %>
10
10
  </style>
11
- <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.8/d3.min.js" type="text/javascript"></script>
11
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.8/d3.min.js"></script>
12
12
  </head>
13
13
  <body>
14
14
  <div id="wrapper">
@@ -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,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "erb"
4
3
  require "uri"
5
- require "action_dispatch/http/request"
6
4
  require "active_support/actionable_error"
7
5
 
8
6
  module ActionDispatch
@@ -31,15 +29,15 @@ module ActionDispatch
31
29
  uri = URI.parse location
32
30
 
33
31
  if uri.relative? || uri.scheme == "http" || uri.scheme == "https"
34
- body = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(location)}\">redirected</a>.</body></html>"
32
+ body = ""
35
33
  else
36
- return [400, { "Content-Type" => "text/plain" }, ["Invalid redirection URI"]]
34
+ return [400, { Rack::CONTENT_TYPE => "text/plain; charset=utf-8" }, ["Invalid redirection URI"]]
37
35
  end
38
36
 
39
37
  [302, {
40
- "Content-Type" => "text/html; charset=#{Response.default_charset}",
41
- "Content-Length" => body.bytesize.to_s,
42
- "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,
43
41
  }, [body]]
44
42
  end
45
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
@@ -7,7 +7,7 @@ require "active_support/json"
7
7
  require "rack/utils"
8
8
 
9
9
  module ActionDispatch
10
- class Request
10
+ module RequestCookieMethods
11
11
  def cookie_jar
12
12
  fetch_header("action_dispatch.cookies") do
13
13
  self.cookie_jar = Cookies::CookieJar.build(self, cookies)
@@ -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
@@ -88,10 +88,14 @@ module ActionDispatch
88
88
  # :startdoc:
89
89
  end
90
90
 
91
- # Read and write data to cookies through ActionController#cookies.
91
+ ActiveSupport.on_load(:action_dispatch_request) do
92
+ include RequestCookieMethods
93
+ end
94
+
95
+ # Read and write data to cookies through ActionController::Cookies#cookies.
92
96
  #
93
97
  # When reading cookie data, the data is read from the HTTP request header, Cookie.
94
- # When writing cookie data, the data is sent out in the HTTP response header, Set-Cookie.
98
+ # When writing cookie data, the data is sent out in the HTTP response header, +Set-Cookie+.
95
99
  #
96
100
  # Examples of writing:
97
101
  #
@@ -99,7 +103,7 @@ module ActionDispatch
99
103
  # # This cookie will be deleted when the user's browser is closed.
100
104
  # cookies[:user_name] = "david"
101
105
  #
102
- # # Cookie values are String based. Other data types need to be serialized.
106
+ # # Cookie values are String-based. Other data types need to be serialized.
103
107
  # cookies[:lat_lon] = JSON.generate([47.68, -122.37])
104
108
  #
105
109
  # # Sets a cookie that expires in 1 hour.
@@ -135,7 +139,7 @@ module ActionDispatch
135
139
  #
136
140
  # cookies.delete :user_name
137
141
  #
138
- # Please note that if you specify a :domain when setting a cookie, you must also specify the domain when deleting the cookie:
142
+ # Please note that if you specify a +:domain+ when setting a cookie, you must also specify the domain when deleting the cookie:
139
143
  #
140
144
  # cookies[:name] = {
141
145
  # value: 'a yummy cookie',
@@ -156,13 +160,18 @@ module ActionDispatch
156
160
  # to <tt>:all</tt>. To support multiple domains, provide an array, and
157
161
  # the first domain matching <tt>request.host</tt> will be used. Make
158
162
  # sure to specify the <tt>:domain</tt> option with <tt>:all</tt> or
159
- # <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.
160
166
  #
161
167
  # domain: nil # Does not set cookie domain. (default)
162
168
  # domain: :all # Allow the cookie for the top most level
163
169
  # # domain and subdomains.
164
170
  # domain: %w(.example.com .example.org) # Allow the cookie
165
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
+ #
166
175
  #
167
176
  # * <tt>:tld_length</tt> - When using <tt>:domain => :all</tt>, this option can be used to explicitly
168
177
  # set the TLD length when using a short (<= 3 character) domain that is being interpreted as part of a TLD.
@@ -172,6 +181,10 @@ module ActionDispatch
172
181
  # Default is +false+.
173
182
  # * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
174
183
  # only HTTP. Defaults to +false+.
184
+ # * <tt>:same_site</tt> - The value of the +SameSite+ cookie attribute, which
185
+ # determines how this cookie should be restricted in cross-site contexts.
186
+ # Possible values are +nil+, +:none+, +:lax+, and +:strict+. Defaults to
187
+ # +:lax+.
175
188
  class Cookies
176
189
  HTTP_HEADER = "Set-Cookie"
177
190
  GENERATOR_KEY = "action_dispatch.key_generator"
@@ -195,7 +208,7 @@ module ActionDispatch
195
208
  # Raised when storing more than 4K of session data.
196
209
  CookieOverflow = Class.new StandardError
197
210
 
198
- # Include in a cookie jar to allow chaining, e.g. cookies.permanent.signed.
211
+ # Include in a cookie jar to allow chaining, e.g. +cookies.permanent.signed+.
199
212
  module ChainedCookieJars
200
213
  # Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example:
201
214
  #
@@ -280,7 +293,7 @@ module ActionDispatch
280
293
  end
281
294
  end
282
295
 
283
- class CookieJar #:nodoc:
296
+ class CookieJar # :nodoc:
284
297
  include Enumerable, ChainedCookieJars
285
298
 
286
299
  def self.build(req, cookies)
@@ -369,6 +382,8 @@ module ActionDispatch
369
382
  # Removes the cookie on the client machine by setting the value to an empty string
370
383
  # and the expiration date in the past. Like <tt>[]=</tt>, you can pass in
371
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.
372
387
  def delete(name, options = {})
373
388
  return unless @cookies.has_key? name.to_s
374
389
 
@@ -394,9 +409,15 @@ module ActionDispatch
394
409
  @cookies.each_key { |k| delete(k, options) }
395
410
  end
396
411
 
397
- def write(headers)
398
- if header = make_set_cookie_header(headers[HTTP_HEADER])
399
- 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)
400
421
  end
401
422
  end
402
423
 
@@ -407,21 +428,8 @@ module ActionDispatch
407
428
  ::Rack::Utils.escape(string)
408
429
  end
409
430
 
410
- def make_set_cookie_header(header)
411
- header = @set_cookies.inject(header) { |m, (k, v)|
412
- if write_cookie?(v)
413
- ::Rack::Utils.add_cookie_to_header(m, k, v)
414
- else
415
- m
416
- end
417
- }
418
- @delete_cookies.inject(header) { |m, (k, v)|
419
- ::Rack::Utils.add_remove_cookie_to_header(m, k, v)
420
- }
421
- end
422
-
423
431
  def write_cookie?(cookie)
424
- request.ssl? || !cookie[:secure] || always_write_cookie
432
+ request.ssl? || !cookie[:secure] || always_write_cookie || request.host.end_with?(".onion")
425
433
  end
426
434
 
427
435
  def handle_options(options)
@@ -431,12 +439,13 @@ module ActionDispatch
431
439
 
432
440
  options[:path] ||= "/"
433
441
 
434
- cookies_same_site_protection = request.cookies_same_site_protection
435
- 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
436
445
 
437
446
  if options[:domain] == :all || options[:domain] == "all"
438
447
  cookie_domain = ""
439
- dot_splitted_host = request.host.split('.', -1)
448
+ dot_splitted_host = request.host.split(".", -1)
440
449
 
441
450
  # Case where request.host is not an IP address or it's an invalid domain
442
451
  # (ip confirms to the domain structure we expect so we explicitly check for ip)
@@ -446,10 +455,10 @@ module ActionDispatch
446
455
  end
447
456
 
448
457
  # If there is a provided tld length then we use it otherwise default domain.
449
- if options[:tld_length].present?
458
+ if options[:tld_length].present?
450
459
  # Case where the tld_length provided is valid
451
460
  if dot_splitted_host.length >= options[:tld_length]
452
- cookie_domain = dot_splitted_host.last(options[:tld_length]).join('.')
461
+ cookie_domain = dot_splitted_host.last(options[:tld_length]).join(".")
453
462
  end
454
463
  # Case where tld_length is not provided
455
464
  else
@@ -458,12 +467,12 @@ module ActionDispatch
458
467
  cookie_domain = dot_splitted_host.last(2).join(".")
459
468
  # **.**, ***.** style TLDs like co.uk and com.au
460
469
  else
461
- cookie_domain = dot_splitted_host.last(3).join('.')
470
+ cookie_domain = dot_splitted_host.last(3).join(".")
462
471
  end
463
472
  end
464
473
 
465
474
  options[:domain] = if cookie_domain.present?
466
- ".#{cookie_domain}"
475
+ cookie_domain
467
476
  end
468
477
  elsif options[:domain].is_a? Array
469
478
  # If host matches one of the supplied domains.
@@ -471,6 +480,8 @@ module ActionDispatch
471
480
  domain = domain.delete_prefix(".")
472
481
  request.host == domain || request.host.end_with?(".#{domain}")
473
482
  end
483
+ elsif options[:domain].respond_to?(:call)
484
+ options[:domain] = options[:domain].call(request)
474
485
  end
475
486
  end
476
487
  end
@@ -534,75 +545,57 @@ module ActionDispatch
534
545
  end
535
546
  end
536
547
 
537
- class MarshalWithJsonFallback # :nodoc:
538
- def self.load(value)
539
- Marshal.load(value)
540
- rescue TypeError => e
541
- ActiveSupport::JSON.decode(value) rescue raise e
542
- end
543
-
544
- def self.dump(value)
545
- Marshal.dump(value)
546
- end
547
- end
548
-
549
- class JsonSerializer # :nodoc:
550
- def self.load(value)
551
- ActiveSupport::JSON.decode(value)
552
- end
553
-
554
- def self.dump(value)
555
- ActiveSupport::JSON.encode(value)
556
- end
557
- end
558
-
559
548
  module SerializedCookieJars # :nodoc:
560
- MARSHAL_SIGNATURE = "\x04\x08"
561
549
  SERIALIZER = ActiveSupport::MessageEncryptor::NullSerializer
562
550
 
563
551
  protected
564
- def needs_migration?(value)
565
- request.cookies_serializer == :hybrid && value.start_with?(MARSHAL_SIGNATURE)
552
+ def digest
553
+ request.cookies_digest || "SHA1"
566
554
  end
567
555
 
568
- def serialize(value)
569
- 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
570
569
  end
571
570
 
572
- def deserialize(name)
573
- rotate = false
574
- 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
575
576
 
576
- if value
577
- case
578
- when needs_migration?(value)
579
- Marshal.load(value).tap do |v|
580
- self[name] = { value: v }
581
- end
582
- when rotate
583
- serializer.load(value).tap do |v|
584
- self[name] = { value: v }
585
- end
586
- else
587
- 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
588
583
  end
584
+
585
+ self[name] = { value: value } if force_reserialize || reserialize?(dumped)
586
+
587
+ value
589
588
  end
590
589
  end
591
590
 
592
- def serializer
593
- serializer = request.cookies_serializer || :marshal
594
- case serializer
595
- when :marshal
596
- MarshalWithJsonFallback
597
- when :json, :hybrid
598
- JsonSerializer
599
- else
600
- serializer
601
- end
591
+ def commit(name, options)
592
+ options[:value] = serializer.dump(options[:value])
602
593
  end
603
594
 
604
- def digest
605
- 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
606
599
  end
607
600
  end
608
601
 
@@ -623,15 +616,15 @@ module ActionDispatch
623
616
 
624
617
  private
625
618
  def parse(name, signed_message, purpose: nil)
626
- deserialize(name) do |rotate|
627
- @verifier.verified(signed_message, on_rotation: rotate, purpose: purpose)
628
- end
619
+ rotated = false
620
+ data = @verifier.verified(signed_message, purpose: purpose, on_rotation: -> { rotated = true })
621
+ super(name, data, force_reserialize: rotated)
629
622
  end
630
623
 
631
624
  def commit(name, options)
632
- options[:value] = @verifier.generate(serialize(options[:value]), **cookie_metadata(name, options))
633
-
634
- 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)
635
628
  end
636
629
  end
637
630
 
@@ -673,17 +666,17 @@ module ActionDispatch
673
666
 
674
667
  private
675
668
  def parse(name, encrypted_message, purpose: nil)
676
- deserialize(name) do |rotate|
677
- @encryptor.decrypt_and_verify(encrypted_message, on_rotation: rotate, purpose: purpose)
678
- 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)
679
672
  rescue ActiveSupport::MessageEncryptor::InvalidMessage, ActiveSupport::MessageVerifier::InvalidSignature
680
673
  nil
681
674
  end
682
675
 
683
676
  def commit(name, options)
684
- options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value]), **cookie_metadata(name, options))
685
-
686
- 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)
687
680
  end
688
681
  end
689
682
 
@@ -692,21 +685,18 @@ module ActionDispatch
692
685
  end
693
686
 
694
687
  def call(env)
695
- request = ActionDispatch::Request.new env
696
-
697
- status, headers, body = @app.call(env)
688
+ request = ActionDispatch::Request.new(env)
689
+ response = @app.call(env)
698
690
 
699
691
  if request.have_cookie_jar?
700
692
  cookie_jar = request.cookie_jar
701
693
  unless cookie_jar.committed?
702
- cookie_jar.write(headers)
703
- if headers[HTTP_HEADER].respond_to?(:join)
704
- headers[HTTP_HEADER] = headers[HTTP_HEADER].join("\n")
705
- end
694
+ response = Rack::Response[*response]
695
+ cookie_jar.write(response)
706
696
  end
707
697
  end
708
698
 
709
- [status, headers, body]
699
+ response.to_a
710
700
  end
711
701
  end
712
702
  end