actionpack 7.0.8.1 → 7.2.2.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 (171) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +94 -500
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -2
  5. data/lib/abstract_controller/asset_paths.rb +2 -0
  6. data/lib/abstract_controller/base.rb +119 -106
  7. data/lib/abstract_controller/caching/fragments.rb +51 -52
  8. data/lib/abstract_controller/caching.rb +2 -0
  9. data/lib/abstract_controller/callbacks.rb +94 -67
  10. data/lib/abstract_controller/collector.rb +6 -6
  11. data/lib/abstract_controller/deprecator.rb +9 -0
  12. data/lib/abstract_controller/error.rb +2 -0
  13. data/lib/abstract_controller/helpers.rb +121 -91
  14. data/lib/abstract_controller/logger.rb +2 -0
  15. data/lib/abstract_controller/railties/routes_helpers.rb +3 -16
  16. data/lib/abstract_controller/rendering.rb +14 -13
  17. data/lib/abstract_controller/translation.rb +12 -30
  18. data/lib/abstract_controller/url_for.rb +9 -5
  19. data/lib/abstract_controller.rb +8 -0
  20. data/lib/action_controller/api/api_rendering.rb +2 -0
  21. data/lib/action_controller/api.rb +78 -73
  22. data/lib/action_controller/base.rb +199 -141
  23. data/lib/action_controller/caching.rb +16 -11
  24. data/lib/action_controller/deprecator.rb +9 -0
  25. data/lib/action_controller/form_builder.rb +21 -16
  26. data/lib/action_controller/log_subscriber.rb +19 -5
  27. data/lib/action_controller/metal/allow_browser.rb +123 -0
  28. data/lib/action_controller/metal/basic_implicit_render.rb +2 -0
  29. data/lib/action_controller/metal/conditional_get.rb +187 -174
  30. data/lib/action_controller/metal/content_security_policy.rb +26 -25
  31. data/lib/action_controller/metal/cookies.rb +4 -2
  32. data/lib/action_controller/metal/data_streaming.rb +65 -54
  33. data/lib/action_controller/metal/default_headers.rb +6 -2
  34. data/lib/action_controller/metal/etag_with_flash.rb +4 -0
  35. data/lib/action_controller/metal/etag_with_template_digest.rb +18 -14
  36. data/lib/action_controller/metal/exceptions.rb +19 -9
  37. data/lib/action_controller/metal/flash.rb +12 -10
  38. data/lib/action_controller/metal/head.rb +20 -16
  39. data/lib/action_controller/metal/helpers.rb +64 -67
  40. data/lib/action_controller/metal/http_authentication.rb +214 -200
  41. data/lib/action_controller/metal/implicit_render.rb +21 -17
  42. data/lib/action_controller/metal/instrumentation.rb +22 -12
  43. data/lib/action_controller/metal/live.rb +125 -92
  44. data/lib/action_controller/metal/logging.rb +6 -4
  45. data/lib/action_controller/metal/mime_responds.rb +151 -142
  46. data/lib/action_controller/metal/parameter_encoding.rb +34 -32
  47. data/lib/action_controller/metal/params_wrapper.rb +58 -58
  48. data/lib/action_controller/metal/permissions_policy.rb +14 -13
  49. data/lib/action_controller/metal/rate_limiting.rb +62 -0
  50. data/lib/action_controller/metal/redirecting.rb +110 -84
  51. data/lib/action_controller/metal/renderers.rb +50 -49
  52. data/lib/action_controller/metal/rendering.rb +103 -82
  53. data/lib/action_controller/metal/request_forgery_protection.rb +279 -161
  54. data/lib/action_controller/metal/rescue.rb +12 -8
  55. data/lib/action_controller/metal/streaming.rb +174 -132
  56. data/lib/action_controller/metal/strong_parameters.rb +598 -473
  57. data/lib/action_controller/metal/testing.rb +2 -0
  58. data/lib/action_controller/metal/url_for.rb +23 -14
  59. data/lib/action_controller/metal.rb +145 -61
  60. data/lib/action_controller/railtie.rb +25 -9
  61. data/lib/action_controller/railties/helpers.rb +2 -0
  62. data/lib/action_controller/renderer.rb +105 -66
  63. data/lib/action_controller/template_assertions.rb +4 -2
  64. data/lib/action_controller/test_case.rb +157 -128
  65. data/lib/action_controller.rb +17 -3
  66. data/lib/action_dispatch/constants.rb +34 -0
  67. data/lib/action_dispatch/deprecator.rb +9 -0
  68. data/lib/action_dispatch/http/cache.rb +28 -29
  69. data/lib/action_dispatch/http/content_disposition.rb +2 -0
  70. data/lib/action_dispatch/http/content_security_policy.rb +69 -49
  71. data/lib/action_dispatch/http/filter_parameters.rb +27 -12
  72. data/lib/action_dispatch/http/filter_redirect.rb +22 -1
  73. data/lib/action_dispatch/http/headers.rb +23 -21
  74. data/lib/action_dispatch/http/mime_negotiation.rb +37 -48
  75. data/lib/action_dispatch/http/mime_type.rb +60 -30
  76. data/lib/action_dispatch/http/mime_types.rb +5 -1
  77. data/lib/action_dispatch/http/parameters.rb +12 -10
  78. data/lib/action_dispatch/http/permissions_policy.rb +32 -34
  79. data/lib/action_dispatch/http/rack_cache.rb +4 -0
  80. data/lib/action_dispatch/http/request.rb +132 -79
  81. data/lib/action_dispatch/http/response.rb +136 -103
  82. data/lib/action_dispatch/http/upload.rb +19 -15
  83. data/lib/action_dispatch/http/url.rb +75 -73
  84. data/lib/action_dispatch/journey/formatter.rb +19 -6
  85. data/lib/action_dispatch/journey/gtg/builder.rb +4 -3
  86. data/lib/action_dispatch/journey/gtg/simulator.rb +2 -0
  87. data/lib/action_dispatch/journey/gtg/transition_table.rb +10 -8
  88. data/lib/action_dispatch/journey/nfa/dot.rb +2 -0
  89. data/lib/action_dispatch/journey/nodes/node.rb +6 -5
  90. data/lib/action_dispatch/journey/parser.rb +4 -3
  91. data/lib/action_dispatch/journey/parser_extras.rb +2 -0
  92. data/lib/action_dispatch/journey/path/pattern.rb +18 -15
  93. data/lib/action_dispatch/journey/route.rb +12 -9
  94. data/lib/action_dispatch/journey/router/utils.rb +16 -15
  95. data/lib/action_dispatch/journey/router.rb +13 -10
  96. data/lib/action_dispatch/journey/routes.rb +6 -4
  97. data/lib/action_dispatch/journey/scanner.rb +4 -2
  98. data/lib/action_dispatch/journey/visitors.rb +2 -0
  99. data/lib/action_dispatch/journey.rb +2 -0
  100. data/lib/action_dispatch/log_subscriber.rb +25 -0
  101. data/lib/action_dispatch/middleware/actionable_exceptions.rb +7 -6
  102. data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
  103. data/lib/action_dispatch/middleware/callbacks.rb +4 -0
  104. data/lib/action_dispatch/middleware/cookies.rb +192 -194
  105. data/lib/action_dispatch/middleware/debug_exceptions.rb +36 -27
  106. data/lib/action_dispatch/middleware/debug_locks.rb +18 -13
  107. data/lib/action_dispatch/middleware/debug_view.rb +9 -2
  108. data/lib/action_dispatch/middleware/exception_wrapper.rb +181 -27
  109. data/lib/action_dispatch/middleware/executor.rb +9 -1
  110. data/lib/action_dispatch/middleware/flash.rb +65 -46
  111. data/lib/action_dispatch/middleware/host_authorization.rb +22 -17
  112. data/lib/action_dispatch/middleware/public_exceptions.rb +12 -8
  113. data/lib/action_dispatch/middleware/reloader.rb +9 -5
  114. data/lib/action_dispatch/middleware/remote_ip.rb +88 -83
  115. data/lib/action_dispatch/middleware/request_id.rb +15 -8
  116. data/lib/action_dispatch/middleware/server_timing.rb +8 -6
  117. data/lib/action_dispatch/middleware/session/abstract_store.rb +7 -0
  118. data/lib/action_dispatch/middleware/session/cache_store.rb +14 -7
  119. data/lib/action_dispatch/middleware/session/cookie_store.rb +32 -25
  120. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +9 -3
  121. data/lib/action_dispatch/middleware/show_exceptions.rb +42 -28
  122. data/lib/action_dispatch/middleware/ssl.rb +60 -45
  123. data/lib/action_dispatch/middleware/stack.rb +15 -9
  124. data/lib/action_dispatch/middleware/static.rb +40 -34
  125. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
  126. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +4 -4
  127. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
  128. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +7 -7
  129. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
  130. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +17 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +16 -12
  132. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -1
  133. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
  134. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +4 -4
  135. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  136. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  137. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +3 -0
  138. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +47 -38
  139. data/lib/action_dispatch/railtie.rb +12 -4
  140. data/lib/action_dispatch/request/session.rb +39 -27
  141. data/lib/action_dispatch/request/utils.rb +10 -3
  142. data/lib/action_dispatch/routing/endpoint.rb +2 -0
  143. data/lib/action_dispatch/routing/inspector.rb +59 -9
  144. data/lib/action_dispatch/routing/mapper.rb +686 -639
  145. data/lib/action_dispatch/routing/polymorphic_routes.rb +70 -61
  146. data/lib/action_dispatch/routing/redirection.rb +52 -38
  147. data/lib/action_dispatch/routing/route_set.rb +106 -62
  148. data/lib/action_dispatch/routing/routes_proxy.rb +16 -19
  149. data/lib/action_dispatch/routing/url_for.rb +131 -122
  150. data/lib/action_dispatch/routing.rb +152 -150
  151. data/lib/action_dispatch/system_test_case.rb +91 -81
  152. data/lib/action_dispatch/system_testing/browser.rb +27 -19
  153. data/lib/action_dispatch/system_testing/driver.rb +16 -22
  154. data/lib/action_dispatch/system_testing/server.rb +2 -0
  155. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +53 -31
  156. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +2 -0
  157. data/lib/action_dispatch/testing/assertion_response.rb +9 -7
  158. data/lib/action_dispatch/testing/assertions/response.rb +36 -26
  159. data/lib/action_dispatch/testing/assertions/routing.rb +203 -95
  160. data/lib/action_dispatch/testing/assertions.rb +5 -1
  161. data/lib/action_dispatch/testing/integration.rb +240 -229
  162. data/lib/action_dispatch/testing/request_encoder.rb +6 -1
  163. data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
  164. data/lib/action_dispatch/testing/test_process.rb +14 -9
  165. data/lib/action_dispatch/testing/test_request.rb +4 -2
  166. data/lib/action_dispatch/testing/test_response.rb +34 -19
  167. data/lib/action_dispatch.rb +52 -21
  168. data/lib/action_pack/gem_version.rb +5 -3
  169. data/lib/action_pack/version.rb +3 -1
  170. data/lib/action_pack.rb +18 -17
  171. metadata +91 -32
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "active_support/core_ext/hash/keys"
4
6
  require "active_support/key_generator"
5
7
  require "active_support/message_verifier"
@@ -70,7 +72,7 @@ module ActionDispatch
70
72
  end
71
73
 
72
74
  def cookies_same_site_protection
73
- get_header(Cookies::COOKIES_SAME_SITE_PROTECTION) || Proc.new { }
75
+ get_header(Cookies::COOKIES_SAME_SITE_PROTECTION)&.call(self)
74
76
  end
75
77
 
76
78
  def cookies_digest
@@ -92,93 +94,102 @@ module ActionDispatch
92
94
  include RequestCookieMethods
93
95
  end
94
96
 
95
- # Read and write data to cookies through ActionController::Base#cookies.
97
+ # Read and write data to cookies through ActionController::Cookies#cookies.
96
98
  #
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+.
99
+ # When reading cookie data, the data is read from the HTTP request header,
100
+ # Cookie. When writing cookie data, the data is sent out in the HTTP response
101
+ # header, `Set-Cookie`.
99
102
  #
100
103
  # Examples of writing:
101
104
  #
102
- # # Sets a simple session cookie.
103
- # # This cookie will be deleted when the user's browser is closed.
104
- # cookies[:user_name] = "david"
105
+ # # Sets a simple session cookie.
106
+ # # This cookie will be deleted when the user's browser is closed.
107
+ # cookies[:user_name] = "david"
105
108
  #
106
- # # Cookie values are String-based. Other data types need to be serialized.
107
- # cookies[:lat_lon] = JSON.generate([47.68, -122.37])
109
+ # # Cookie values are String-based. Other data types need to be serialized.
110
+ # cookies[:lat_lon] = JSON.generate([47.68, -122.37])
108
111
  #
109
- # # Sets a cookie that expires in 1 hour.
110
- # cookies[:login] = { value: "XJ-122", expires: 1.hour }
112
+ # # Sets a cookie that expires in 1 hour.
113
+ # cookies[:login] = { value: "XJ-122", expires: 1.hour }
111
114
  #
112
- # # Sets a cookie that expires at a specific time.
113
- # cookies[:login] = { value: "XJ-122", expires: Time.utc(2020, 10, 15, 5) }
115
+ # # Sets a cookie that expires at a specific time.
116
+ # cookies[:login] = { value: "XJ-122", expires: Time.utc(2020, 10, 15, 5) }
114
117
  #
115
- # # Sets a signed cookie, which prevents users from tampering with its value.
116
- # # It can be read using the signed method `cookies.signed[:name]`
117
- # cookies.signed[:user_id] = current_user.id
118
+ # # Sets a signed cookie, which prevents users from tampering with its value.
119
+ # # It can be read using the signed method `cookies.signed[:name]`
120
+ # cookies.signed[:user_id] = current_user.id
118
121
  #
119
- # # Sets an encrypted cookie value before sending it to the client which
120
- # # prevent users from reading and tampering with its value.
121
- # # It can be read using the encrypted method `cookies.encrypted[:name]`
122
- # cookies.encrypted[:discount] = 45
122
+ # # Sets an encrypted cookie value before sending it to the client which
123
+ # # prevent users from reading and tampering with its value.
124
+ # # It can be read using the encrypted method `cookies.encrypted[:name]`
125
+ # cookies.encrypted[:discount] = 45
123
126
  #
124
- # # Sets a "permanent" cookie (which expires in 20 years from now).
125
- # cookies.permanent[:login] = "XJ-122"
127
+ # # Sets a "permanent" cookie (which expires in 20 years from now).
128
+ # cookies.permanent[:login] = "XJ-122"
126
129
  #
127
- # # You can also chain these methods:
128
- # cookies.signed.permanent[:login] = "XJ-122"
130
+ # # You can also chain these methods:
131
+ # cookies.signed.permanent[:login] = "XJ-122"
129
132
  #
130
133
  # Examples of reading:
131
134
  #
132
- # cookies[:user_name] # => "david"
133
- # cookies.size # => 2
134
- # JSON.parse(cookies[:lat_lon]) # => [47.68, -122.37]
135
- # cookies.signed[:login] # => "XJ-122"
136
- # cookies.encrypted[:discount] # => 45
135
+ # cookies[:user_name] # => "david"
136
+ # cookies.size # => 2
137
+ # JSON.parse(cookies[:lat_lon]) # => [47.68, -122.37]
138
+ # cookies.signed[:login] # => "XJ-122"
139
+ # cookies.encrypted[:discount] # => 45
137
140
  #
138
141
  # Example for deleting:
139
142
  #
140
- # cookies.delete :user_name
143
+ # cookies.delete :user_name
141
144
  #
142
- # Please note that if you specify a +:domain+ when setting a cookie, you must also specify the domain when deleting the cookie:
145
+ # Please note that if you specify a `:domain` when setting a cookie, you must
146
+ # also specify the domain when deleting the cookie:
143
147
  #
144
- # cookies[:name] = {
145
- # value: 'a yummy cookie',
146
- # expires: 1.year,
147
- # domain: 'domain.com'
148
- # }
148
+ # cookies[:name] = {
149
+ # value: 'a yummy cookie',
150
+ # expires: 1.year,
151
+ # domain: 'domain.com'
152
+ # }
149
153
  #
150
- # cookies.delete(:name, domain: 'domain.com')
154
+ # cookies.delete(:name, domain: 'domain.com')
151
155
  #
152
156
  # The option symbols for setting cookies are:
153
157
  #
154
- # * <tt>:value</tt> - The cookie's value.
155
- # * <tt>:path</tt> - The path for which this cookie applies. Defaults to the root
156
- # of the application.
157
- # * <tt>:domain</tt> - The domain for which this cookie applies so you can
158
- # restrict to the domain level. If you use a schema like www.example.com
159
- # and want to share session with user.example.com set <tt>:domain</tt>
160
- # to <tt>:all</tt>. To support multiple domains, provide an array, and
161
- # the first domain matching <tt>request.host</tt> will be used. Make
162
- # sure to specify the <tt>:domain</tt> option with <tt>:all</tt> or
163
- # <tt>Array</tt> again when deleting cookies.
158
+ # * `:value` - The cookie's value.
159
+ # * `:path` - The path for which this cookie applies. Defaults to the root of
160
+ # the application.
161
+ # * `:domain` - The domain for which this cookie applies so you can restrict
162
+ # to the domain level. If you use a schema like www.example.com and want to
163
+ # share session with user.example.com set `:domain` to `:all`. To support
164
+ # multiple domains, provide an array, and the first domain matching
165
+ # `request.host` will be used. Make sure to specify the `:domain` option
166
+ # with `:all` or `Array` again when deleting cookies. For more flexibility
167
+ # you can set the domain on a per-request basis by specifying `:domain` with
168
+ # a proc.
169
+ #
170
+ # domain: nil # Does not set cookie domain. (default)
171
+ # domain: :all # Allow the cookie for the top most level
172
+ # # domain and subdomains.
173
+ # domain: %w(.example.com .example.org) # Allow the cookie
174
+ # # for concrete domain names.
175
+ # domain: proc { Tenant.current.cookie_domain } # Set cookie domain dynamically
176
+ # domain: proc { |req| ".sub.#{req.host}" } # Set cookie domain dynamically based on request
164
177
  #
165
- # domain: nil # Does not set cookie domain. (default)
166
- # domain: :all # Allow the cookie for the top most level
167
- # # domain and subdomains.
168
- # domain: %w(.example.com .example.org) # Allow the cookie
169
- # # for concrete domain names.
178
+ # * `:tld_length` - When using `:domain => :all`, this option can be used to
179
+ # explicitly set the TLD length when using a short (<= 3 character) domain
180
+ # that is being interpreted as part of a TLD. For example, to share cookies
181
+ # between user1.lvh.me and user2.lvh.me, set `:tld_length` to 2.
182
+ # * `:expires` - The time at which this cookie expires, as a Time or
183
+ # ActiveSupport::Duration object.
184
+ # * `:secure` - Whether this cookie is only transmitted to HTTPS servers.
185
+ # Default is `false`.
186
+ # * `:httponly` - Whether this cookie is accessible via scripting or only
187
+ # HTTP. Defaults to `false`.
188
+ # * `:same_site` - The value of the `SameSite` cookie attribute, which
189
+ # determines how this cookie should be restricted in cross-site contexts.
190
+ # Possible values are `nil`, `:none`, `:lax`, and `:strict`. Defaults to
191
+ # `:lax`.
170
192
  #
171
- # * <tt>:tld_length</tt> - When using <tt>:domain => :all</tt>, this option can be used to explicitly
172
- # set the TLD length when using a short (<= 3 character) domain that is being interpreted as part of a TLD.
173
- # For example, to share cookies between user1.lvh.me and user2.lvh.me, set <tt>:tld_length</tt> to 2.
174
- # * <tt>:expires</tt> - The time at which this cookie expires, as a \Time or ActiveSupport::Duration object.
175
- # * <tt>:secure</tt> - Whether this cookie is only transmitted to HTTPS servers.
176
- # Default is +false+.
177
- # * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
178
- # only HTTP. Defaults to +false+.
179
- # * <tt>:same_site</tt> - The value of the +SameSite+ cookie attribute, which
180
- # determines how this cookie should be restricted in cross-site contexts.
181
- # Possible values are +:none+, +:lax+, and +:strict+. Defaults to +:lax+.
182
193
  class Cookies
183
194
  HTTP_HEADER = "Set-Cookie"
184
195
  GENERATOR_KEY = "action_dispatch.key_generator"
@@ -202,59 +213,69 @@ module ActionDispatch
202
213
  # Raised when storing more than 4K of session data.
203
214
  CookieOverflow = Class.new StandardError
204
215
 
205
- # Include in a cookie jar to allow chaining, e.g. +cookies.permanent.signed+.
216
+ # Include in a cookie jar to allow chaining, e.g. `cookies.permanent.signed`.
206
217
  module ChainedCookieJars
207
- # Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example:
218
+ # Returns a jar that'll automatically set the assigned cookies to have an
219
+ # expiration date 20 years from now. Example:
208
220
  #
209
- # cookies.permanent[:prefers_open_id] = true
210
- # # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
221
+ # cookies.permanent[:prefers_open_id] = true
222
+ # # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
211
223
  #
212
- # This jar is only meant for writing. You'll read permanent cookies through the regular accessor.
224
+ # This jar is only meant for writing. You'll read permanent cookies through the
225
+ # regular accessor.
213
226
  #
214
- # This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples:
227
+ # This jar allows chaining with the signed jar as well, so you can set
228
+ # permanent, signed cookies. Examples:
215
229
  #
216
- # cookies.permanent.signed[:remember_me] = current_user.id
217
- # # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
230
+ # cookies.permanent.signed[:remember_me] = current_user.id
231
+ # # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
218
232
  def permanent
219
233
  @permanent ||= PermanentCookieJar.new(self)
220
234
  end
221
235
 
222
- # Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
223
- # the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
224
- # cookie was tampered with by the user (or a 3rd party), +nil+ will be returned.
236
+ # Returns a jar that'll automatically generate a signed representation of cookie
237
+ # value and verify it when reading from the cookie again. This is useful for
238
+ # creating cookies with values that the user is not supposed to change. If a
239
+ # signed cookie was tampered with by the user (or a 3rd party), `nil` will be
240
+ # returned.
225
241
  #
226
- # This jar requires that you set a suitable secret for the verification on your app's +secret_key_base+.
242
+ # This jar requires that you set a suitable secret for the verification on your
243
+ # app's `secret_key_base`.
227
244
  #
228
245
  # Example:
229
246
  #
230
- # cookies.signed[:discount] = 45
231
- # # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
247
+ # cookies.signed[:discount] = 45
248
+ # # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
232
249
  #
233
- # cookies.signed[:discount] # => 45
250
+ # cookies.signed[:discount] # => 45
234
251
  def signed
235
252
  @signed ||= SignedKeyRotatingCookieJar.new(self)
236
253
  end
237
254
 
238
- # Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
239
- # If the cookie was tampered with by the user (or a 3rd party), +nil+ will be returned.
255
+ # Returns a jar that'll automatically encrypt cookie values before sending them
256
+ # to the client and will decrypt them for read. If the cookie was tampered with
257
+ # by the user (or a 3rd party), `nil` will be returned.
240
258
  #
241
- # If +config.action_dispatch.encrypted_cookie_salt+ and +config.action_dispatch.encrypted_signed_cookie_salt+
242
- # are both set, legacy cookies encrypted with HMAC AES-256-CBC will be transparently upgraded.
259
+ # If `config.action_dispatch.encrypted_cookie_salt` and
260
+ # `config.action_dispatch.encrypted_signed_cookie_salt` are both set, legacy
261
+ # cookies encrypted with HMAC AES-256-CBC will be transparently upgraded.
243
262
  #
244
- # This jar requires that you set a suitable secret for the verification on your app's +secret_key_base+.
263
+ # This jar requires that you set a suitable secret for the verification on your
264
+ # app's `secret_key_base`.
245
265
  #
246
266
  # Example:
247
267
  #
248
- # cookies.encrypted[:discount] = 45
249
- # # => Set-Cookie: discount=DIQ7fw==--K3n//8vvnSbGq9dA--7Xh91HfLpwzbj1czhBiwOg==; path=/
268
+ # cookies.encrypted[:discount] = 45
269
+ # # => Set-Cookie: discount=DIQ7fw==--K3n//8vvnSbGq9dA--7Xh91HfLpwzbj1czhBiwOg==; path=/
250
270
  #
251
- # cookies.encrypted[:discount] # => 45
271
+ # cookies.encrypted[:discount] # => 45
252
272
  def encrypted
253
273
  @encrypted ||= EncryptedKeyRotatingCookieJar.new(self)
254
274
  end
255
275
 
256
- # Returns the +signed+ or +encrypted+ jar, preferring +encrypted+ if +secret_key_base+ is set.
257
- # Used by ActionDispatch::Session::CookieStore to avoid the need to introduce new cookie stores.
276
+ # Returns the `signed` or `encrypted` jar, preferring `encrypted` if
277
+ # `secret_key_base` is set. Used by ActionDispatch::Session::CookieStore to
278
+ # avoid the need to introduce new cookie stores.
258
279
  def signed_or_encrypted
259
280
  @signed_or_encrypted ||=
260
281
  if request.secret_key_base.present?
@@ -318,7 +339,7 @@ module ActionDispatch
318
339
  @cookies.each(&block)
319
340
  end
320
341
 
321
- # Returns the value of the cookie by +name+, or +nil+ if no such cookie exists.
342
+ # Returns the value of the cookie by `name`, or `nil` if no such cookie exists.
322
343
  def [](name)
323
344
  @cookies[name.to_s]
324
345
  end
@@ -351,8 +372,8 @@ module ActionDispatch
351
372
  @cookies.map { |k, v| "#{escape(k)}=#{escape(v)}" }.join "; "
352
373
  end
353
374
 
354
- # Sets the cookie named +name+. The second argument may be the cookie's
355
- # value or a hash of options as documented above.
375
+ # Sets the cookie named `name`. The second argument may be the cookie's value or
376
+ # a hash of options as documented above.
356
377
  def []=(name, options)
357
378
  if options.is_a?(Hash)
358
379
  options.symbolize_keys!
@@ -373,9 +394,11 @@ module ActionDispatch
373
394
  value
374
395
  end
375
396
 
376
- # Removes the cookie on the client machine by setting the value to an empty string
377
- # and the expiration date in the past. Like <tt>[]=</tt>, you can pass in
378
- # an options hash to delete cookies with extra data such as a <tt>:path</tt>.
397
+ # Removes the cookie on the client machine by setting the value to an empty
398
+ # string and the expiration date in the past. Like `[]=`, you can pass in an
399
+ # options hash to delete cookies with extra data such as a `:path`.
400
+ #
401
+ # Returns the value of the cookie, or `nil` if the cookie does not exist.
379
402
  def delete(name, options = {})
380
403
  return unless @cookies.has_key? name.to_s
381
404
 
@@ -387,23 +410,29 @@ module ActionDispatch
387
410
  value
388
411
  end
389
412
 
390
- # Whether the given cookie is to be deleted by this CookieJar.
391
- # Like <tt>[]=</tt>, you can pass in an options hash to test if a
392
- # deletion applies to a specific <tt>:path</tt>, <tt>:domain</tt> etc.
413
+ # Whether the given cookie is to be deleted by this CookieJar. Like `[]=`, you
414
+ # can pass in an options hash to test if a deletion applies to a specific
415
+ # `:path`, `:domain` etc.
393
416
  def deleted?(name, options = {})
394
417
  options.symbolize_keys!
395
418
  handle_options(options)
396
419
  @delete_cookies[name.to_s] == options
397
420
  end
398
421
 
399
- # Removes all cookies on the client machine by calling <tt>delete</tt> for each cookie.
422
+ # Removes all cookies on the client machine by calling `delete` for each cookie.
400
423
  def clear(options = {})
401
424
  @cookies.each_key { |k| delete(k, options) }
402
425
  end
403
426
 
404
- def write(headers)
405
- if header = make_set_cookie_header(headers[HTTP_HEADER])
406
- headers[HTTP_HEADER] = header
427
+ def write(response)
428
+ @set_cookies.each do |name, value|
429
+ if write_cookie?(value)
430
+ response.set_cookie(name, value)
431
+ end
432
+ end
433
+
434
+ @delete_cookies.each do |name, value|
435
+ response.delete_cookie(name, value)
407
436
  end
408
437
  end
409
438
 
@@ -414,19 +443,6 @@ module ActionDispatch
414
443
  ::Rack::Utils.escape(string)
415
444
  end
416
445
 
417
- def make_set_cookie_header(header)
418
- header = @set_cookies.inject(header) { |m, (k, v)|
419
- if write_cookie?(v)
420
- ::Rack::Utils.add_cookie_to_header(m, k, v)
421
- else
422
- m
423
- end
424
- }
425
- @delete_cookies.inject(header) { |m, (k, v)|
426
- ::Rack::Utils.add_remove_cookie_to_header(m, k, v)
427
- }
428
- end
429
-
430
446
  def write_cookie?(cookie)
431
447
  request.ssl? || !cookie[:secure] || always_write_cookie || request.host.end_with?(".onion")
432
448
  end
@@ -438,15 +454,16 @@ module ActionDispatch
438
454
 
439
455
  options[:path] ||= "/"
440
456
 
441
- cookies_same_site_protection = request.cookies_same_site_protection
442
- options[:same_site] ||= cookies_same_site_protection.call(request)
457
+ unless options.key?(:same_site)
458
+ options[:same_site] = request.cookies_same_site_protection
459
+ end
443
460
 
444
461
  if options[:domain] == :all || options[:domain] == "all"
445
462
  cookie_domain = ""
446
463
  dot_splitted_host = request.host.split(".", -1)
447
464
 
448
- # Case where request.host is not an IP address or it's an invalid domain
449
- # (ip confirms to the domain structure we expect so we explicitly check for ip)
465
+ # Case where request.host is not an IP address or it's an invalid domain (ip
466
+ # confirms to the domain structure we expect so we explicitly check for ip)
450
467
  if request.host.match?(/^[\d.]+$/) || dot_splitted_host.include?("") || dot_splitted_host.length == 1
451
468
  options[:domain] = nil
452
469
  return
@@ -470,7 +487,7 @@ module ActionDispatch
470
487
  end
471
488
 
472
489
  options[:domain] = if cookie_domain.present?
473
- ".#{cookie_domain}"
490
+ cookie_domain
474
491
  end
475
492
  elsif options[:domain].is_a? Array
476
493
  # If host matches one of the supplied domains.
@@ -478,6 +495,8 @@ module ActionDispatch
478
495
  domain = domain.delete_prefix(".")
479
496
  request.host == domain || request.host.end_with?(".#{domain}")
480
497
  end
498
+ elsif options[:domain].respond_to?(:call)
499
+ options[:domain] = options[:domain].call(request)
481
500
  end
482
501
  end
483
502
  end
@@ -541,75 +560,57 @@ module ActionDispatch
541
560
  end
542
561
  end
543
562
 
544
- class MarshalWithJsonFallback # :nodoc:
545
- def self.load(value)
546
- Marshal.load(value)
547
- rescue TypeError => e
548
- ActiveSupport::JSON.decode(value) rescue raise e
549
- end
550
-
551
- def self.dump(value)
552
- Marshal.dump(value)
553
- end
554
- end
555
-
556
- class JsonSerializer # :nodoc:
557
- def self.load(value)
558
- ActiveSupport::JSON.decode(value)
559
- end
560
-
561
- def self.dump(value)
562
- ActiveSupport::JSON.encode(value)
563
- end
564
- end
565
-
566
563
  module SerializedCookieJars # :nodoc:
567
- MARSHAL_SIGNATURE = "\x04\x08"
568
564
  SERIALIZER = ActiveSupport::MessageEncryptor::NullSerializer
569
565
 
570
566
  protected
571
- def needs_migration?(value)
572
- request.cookies_serializer == :hybrid && value.start_with?(MARSHAL_SIGNATURE)
567
+ def digest
568
+ request.cookies_digest || "SHA1"
573
569
  end
574
570
 
575
- def serialize(value)
576
- serializer.dump(value)
571
+ private
572
+ def serializer
573
+ @serializer ||=
574
+ case request.cookies_serializer
575
+ when nil
576
+ ActiveSupport::Messages::SerializerWithFallback[:marshal]
577
+ when :hybrid
578
+ ActiveSupport::Messages::SerializerWithFallback[:json_allow_marshal]
579
+ when Symbol
580
+ ActiveSupport::Messages::SerializerWithFallback[request.cookies_serializer]
581
+ else
582
+ request.cookies_serializer
583
+ end
577
584
  end
578
585
 
579
- def deserialize(name)
580
- rotate = false
581
- value = yield -> { rotate = true }
586
+ def reserialize?(dumped)
587
+ serializer.is_a?(ActiveSupport::Messages::SerializerWithFallback) &&
588
+ serializer != ActiveSupport::Messages::SerializerWithFallback[:marshal] &&
589
+ !serializer.dumped?(dumped)
590
+ end
582
591
 
583
- if value
584
- case
585
- when needs_migration?(value)
586
- Marshal.load(value).tap do |v|
587
- self[name] = { value: v }
588
- end
589
- when rotate
590
- serializer.load(value).tap do |v|
591
- self[name] = { value: v }
592
- end
593
- else
594
- serializer.load(value)
592
+ def parse(name, dumped, force_reserialize: false, **)
593
+ if dumped
594
+ begin
595
+ value = serializer.load(dumped)
596
+ rescue StandardError
597
+ return
595
598
  end
599
+
600
+ self[name] = { value: value } if force_reserialize || reserialize?(dumped)
601
+
602
+ value
596
603
  end
597
604
  end
598
605
 
599
- def serializer
600
- serializer = request.cookies_serializer || :marshal
601
- case serializer
602
- when :marshal
603
- MarshalWithJsonFallback
604
- when :json, :hybrid
605
- JsonSerializer
606
- else
607
- serializer
608
- end
606
+ def commit(name, options)
607
+ options[:value] = serializer.dump(options[:value])
609
608
  end
610
609
 
611
- def digest
612
- request.cookies_digest || "SHA1"
610
+ def check_for_overflow!(name, options)
611
+ if options[:value].bytesize > MAX_COOKIE_SIZE
612
+ raise CookieOverflow, "#{name} cookie overflowed with size #{options[:value].bytesize} bytes"
613
+ end
613
614
  end
614
615
  end
615
616
 
@@ -630,15 +631,15 @@ module ActionDispatch
630
631
 
631
632
  private
632
633
  def parse(name, signed_message, purpose: nil)
633
- deserialize(name) do |rotate|
634
- @verifier.verified(signed_message, on_rotation: rotate, purpose: purpose)
635
- end
634
+ rotated = false
635
+ data = @verifier.verified(signed_message, purpose: purpose, on_rotation: -> { rotated = true })
636
+ super(name, data, force_reserialize: rotated)
636
637
  end
637
638
 
638
639
  def commit(name, options)
639
- options[:value] = @verifier.generate(serialize(options[:value]), **cookie_metadata(name, options))
640
-
641
- raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
640
+ super
641
+ options[:value] = @verifier.generate(options[:value], **cookie_metadata(name, options))
642
+ check_for_overflow!(name, options)
642
643
  end
643
644
  end
644
645
 
@@ -680,17 +681,17 @@ module ActionDispatch
680
681
 
681
682
  private
682
683
  def parse(name, encrypted_message, purpose: nil)
683
- deserialize(name) do |rotate|
684
- @encryptor.decrypt_and_verify(encrypted_message, on_rotation: rotate, purpose: purpose)
685
- end
686
- rescue ActiveSupport::MessageEncryptor::InvalidMessage, ActiveSupport::MessageVerifier::InvalidSignature, JSON::ParserError
684
+ rotated = false
685
+ data = @encryptor.decrypt_and_verify(encrypted_message, purpose: purpose, on_rotation: -> { rotated = true })
686
+ super(name, data, force_reserialize: rotated)
687
+ rescue ActiveSupport::MessageEncryptor::InvalidMessage, ActiveSupport::MessageVerifier::InvalidSignature
687
688
  nil
688
689
  end
689
690
 
690
691
  def commit(name, options)
691
- options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value]), **cookie_metadata(name, options))
692
-
693
- raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
692
+ super
693
+ options[:value] = @encryptor.encrypt_and_sign(options[:value], **cookie_metadata(name, options))
694
+ check_for_overflow!(name, options)
694
695
  end
695
696
  end
696
697
 
@@ -699,21 +700,18 @@ module ActionDispatch
699
700
  end
700
701
 
701
702
  def call(env)
702
- request = ActionDispatch::Request.new env
703
-
704
- status, headers, body = @app.call(env)
703
+ request = ActionDispatch::Request.new(env)
704
+ response = @app.call(env)
705
705
 
706
706
  if request.have_cookie_jar?
707
707
  cookie_jar = request.cookie_jar
708
708
  unless cookie_jar.committed?
709
- cookie_jar.write(headers)
710
- if headers[HTTP_HEADER].respond_to?(:join)
711
- headers[HTTP_HEADER] = headers[HTTP_HEADER].join("\n")
712
- end
709
+ response = Rack::Response[*response]
710
+ cookie_jar.write(response)
713
711
  end
714
712
  end
715
713
 
716
- [status, headers, body]
714
+ response.to_a
717
715
  end
718
716
  end
719
717
  end