actionpack 6.1.7.5 → 7.1.3.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.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

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