actionpack 7.0.8.7 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +90 -537
- data/MIT-LICENSE +1 -1
- data/README.rdoc +2 -2
- data/lib/abstract_controller/asset_paths.rb +2 -0
- data/lib/abstract_controller/base.rb +119 -106
- data/lib/abstract_controller/caching/fragments.rb +51 -52
- data/lib/abstract_controller/caching.rb +2 -0
- data/lib/abstract_controller/callbacks.rb +94 -67
- data/lib/abstract_controller/collector.rb +6 -6
- data/lib/abstract_controller/deprecator.rb +9 -0
- data/lib/abstract_controller/error.rb +2 -0
- data/lib/abstract_controller/helpers.rb +121 -91
- data/lib/abstract_controller/logger.rb +2 -0
- data/lib/abstract_controller/railties/routes_helpers.rb +3 -16
- data/lib/abstract_controller/rendering.rb +14 -13
- data/lib/abstract_controller/translation.rb +12 -30
- data/lib/abstract_controller/url_for.rb +9 -5
- data/lib/abstract_controller.rb +8 -0
- data/lib/action_controller/api/api_rendering.rb +2 -0
- data/lib/action_controller/api.rb +78 -73
- data/lib/action_controller/base.rb +199 -141
- data/lib/action_controller/caching.rb +16 -11
- data/lib/action_controller/deprecator.rb +9 -0
- data/lib/action_controller/form_builder.rb +21 -16
- data/lib/action_controller/log_subscriber.rb +19 -5
- data/lib/action_controller/metal/allow_browser.rb +123 -0
- data/lib/action_controller/metal/basic_implicit_render.rb +2 -0
- data/lib/action_controller/metal/conditional_get.rb +187 -174
- data/lib/action_controller/metal/content_security_policy.rb +26 -25
- data/lib/action_controller/metal/cookies.rb +4 -2
- data/lib/action_controller/metal/data_streaming.rb +65 -54
- data/lib/action_controller/metal/default_headers.rb +6 -2
- data/lib/action_controller/metal/etag_with_flash.rb +4 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +18 -14
- data/lib/action_controller/metal/exceptions.rb +19 -9
- data/lib/action_controller/metal/flash.rb +12 -10
- data/lib/action_controller/metal/head.rb +20 -16
- data/lib/action_controller/metal/helpers.rb +64 -67
- data/lib/action_controller/metal/http_authentication.rb +212 -199
- data/lib/action_controller/metal/implicit_render.rb +21 -17
- data/lib/action_controller/metal/instrumentation.rb +22 -12
- data/lib/action_controller/metal/live.rb +125 -92
- data/lib/action_controller/metal/logging.rb +6 -4
- data/lib/action_controller/metal/mime_responds.rb +151 -142
- data/lib/action_controller/metal/parameter_encoding.rb +34 -32
- data/lib/action_controller/metal/params_wrapper.rb +58 -58
- data/lib/action_controller/metal/permissions_policy.rb +14 -13
- data/lib/action_controller/metal/rate_limiting.rb +62 -0
- data/lib/action_controller/metal/redirecting.rb +110 -84
- data/lib/action_controller/metal/renderers.rb +50 -49
- data/lib/action_controller/metal/rendering.rb +103 -82
- data/lib/action_controller/metal/request_forgery_protection.rb +279 -161
- data/lib/action_controller/metal/rescue.rb +12 -8
- data/lib/action_controller/metal/streaming.rb +174 -132
- data/lib/action_controller/metal/strong_parameters.rb +598 -473
- data/lib/action_controller/metal/testing.rb +2 -0
- data/lib/action_controller/metal/url_for.rb +23 -14
- data/lib/action_controller/metal.rb +145 -61
- data/lib/action_controller/railtie.rb +25 -9
- data/lib/action_controller/railties/helpers.rb +2 -0
- data/lib/action_controller/renderer.rb +105 -66
- data/lib/action_controller/template_assertions.rb +4 -2
- data/lib/action_controller/test_case.rb +157 -128
- data/lib/action_controller.rb +17 -3
- data/lib/action_dispatch/constants.rb +34 -0
- data/lib/action_dispatch/deprecator.rb +9 -0
- data/lib/action_dispatch/http/cache.rb +28 -29
- data/lib/action_dispatch/http/content_disposition.rb +2 -0
- data/lib/action_dispatch/http/content_security_policy.rb +48 -45
- data/lib/action_dispatch/http/filter_parameters.rb +18 -8
- data/lib/action_dispatch/http/filter_redirect.rb +22 -1
- data/lib/action_dispatch/http/headers.rb +23 -21
- data/lib/action_dispatch/http/mime_negotiation.rb +37 -48
- data/lib/action_dispatch/http/mime_type.rb +60 -30
- data/lib/action_dispatch/http/mime_types.rb +5 -1
- data/lib/action_dispatch/http/parameters.rb +12 -10
- data/lib/action_dispatch/http/permissions_policy.rb +32 -27
- data/lib/action_dispatch/http/rack_cache.rb +4 -0
- data/lib/action_dispatch/http/request.rb +132 -79
- data/lib/action_dispatch/http/response.rb +136 -103
- data/lib/action_dispatch/http/upload.rb +19 -15
- data/lib/action_dispatch/http/url.rb +75 -73
- data/lib/action_dispatch/journey/formatter.rb +19 -6
- data/lib/action_dispatch/journey/gtg/builder.rb +4 -3
- data/lib/action_dispatch/journey/gtg/simulator.rb +2 -0
- data/lib/action_dispatch/journey/gtg/transition_table.rb +10 -8
- data/lib/action_dispatch/journey/nfa/dot.rb +2 -0
- data/lib/action_dispatch/journey/nodes/node.rb +6 -5
- data/lib/action_dispatch/journey/parser.rb +4 -3
- data/lib/action_dispatch/journey/parser_extras.rb +2 -0
- data/lib/action_dispatch/journey/path/pattern.rb +18 -15
- data/lib/action_dispatch/journey/route.rb +12 -9
- data/lib/action_dispatch/journey/router/utils.rb +16 -15
- data/lib/action_dispatch/journey/router.rb +13 -10
- data/lib/action_dispatch/journey/routes.rb +6 -4
- data/lib/action_dispatch/journey/scanner.rb +4 -2
- data/lib/action_dispatch/journey/visitors.rb +2 -0
- data/lib/action_dispatch/journey.rb +2 -0
- data/lib/action_dispatch/log_subscriber.rb +25 -0
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +7 -6
- data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
- data/lib/action_dispatch/middleware/callbacks.rb +4 -0
- data/lib/action_dispatch/middleware/cookies.rb +192 -194
- data/lib/action_dispatch/middleware/debug_exceptions.rb +36 -27
- data/lib/action_dispatch/middleware/debug_locks.rb +18 -13
- data/lib/action_dispatch/middleware/debug_view.rb +9 -2
- data/lib/action_dispatch/middleware/exception_wrapper.rb +181 -27
- data/lib/action_dispatch/middleware/executor.rb +9 -1
- data/lib/action_dispatch/middleware/flash.rb +65 -46
- data/lib/action_dispatch/middleware/host_authorization.rb +22 -17
- data/lib/action_dispatch/middleware/public_exceptions.rb +12 -8
- data/lib/action_dispatch/middleware/reloader.rb +9 -5
- data/lib/action_dispatch/middleware/remote_ip.rb +88 -83
- data/lib/action_dispatch/middleware/request_id.rb +15 -8
- data/lib/action_dispatch/middleware/server_timing.rb +8 -6
- data/lib/action_dispatch/middleware/session/abstract_store.rb +7 -0
- data/lib/action_dispatch/middleware/session/cache_store.rb +14 -7
- data/lib/action_dispatch/middleware/session/cookie_store.rb +32 -25
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +9 -3
- data/lib/action_dispatch/middleware/show_exceptions.rb +42 -28
- data/lib/action_dispatch/middleware/ssl.rb +60 -45
- data/lib/action_dispatch/middleware/stack.rb +15 -9
- data/lib/action_dispatch/middleware/static.rb +40 -34
- data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +7 -7
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +17 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +16 -12
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +3 -0
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +47 -38
- data/lib/action_dispatch/railtie.rb +12 -4
- data/lib/action_dispatch/request/session.rb +39 -27
- data/lib/action_dispatch/request/utils.rb +10 -3
- data/lib/action_dispatch/routing/endpoint.rb +2 -0
- data/lib/action_dispatch/routing/inspector.rb +59 -9
- data/lib/action_dispatch/routing/mapper.rb +686 -639
- data/lib/action_dispatch/routing/polymorphic_routes.rb +70 -61
- data/lib/action_dispatch/routing/redirection.rb +52 -38
- data/lib/action_dispatch/routing/route_set.rb +106 -62
- data/lib/action_dispatch/routing/routes_proxy.rb +16 -19
- data/lib/action_dispatch/routing/url_for.rb +131 -122
- data/lib/action_dispatch/routing.rb +152 -150
- data/lib/action_dispatch/system_test_case.rb +91 -81
- data/lib/action_dispatch/system_testing/browser.rb +27 -19
- data/lib/action_dispatch/system_testing/driver.rb +16 -22
- data/lib/action_dispatch/system_testing/server.rb +2 -0
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +53 -31
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +2 -0
- data/lib/action_dispatch/testing/assertion_response.rb +9 -7
- data/lib/action_dispatch/testing/assertions/response.rb +36 -26
- data/lib/action_dispatch/testing/assertions/routing.rb +203 -95
- data/lib/action_dispatch/testing/assertions.rb +5 -1
- data/lib/action_dispatch/testing/integration.rb +240 -229
- data/lib/action_dispatch/testing/request_encoder.rb +6 -1
- data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
- data/lib/action_dispatch/testing/test_process.rb +14 -9
- data/lib/action_dispatch/testing/test_request.rb +4 -2
- data/lib/action_dispatch/testing/test_response.rb +34 -19
- data/lib/action_dispatch.rb +52 -21
- data/lib/action_pack/gem_version.rb +6 -4
- data/lib/action_pack/version.rb +3 -1
- data/lib/action_pack.rb +18 -17
- metadata +86 -27
@@ -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)
|
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::
|
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,
|
98
|
-
# When writing cookie data, the data is sent out in the HTTP response
|
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
|
-
#
|
103
|
-
#
|
104
|
-
#
|
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
|
-
#
|
107
|
-
#
|
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
|
-
#
|
110
|
-
#
|
112
|
+
# # Sets a cookie that expires in 1 hour.
|
113
|
+
# cookies[:login] = { value: "XJ-122", expires: 1.hour }
|
111
114
|
#
|
112
|
-
#
|
113
|
-
#
|
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
|
-
#
|
116
|
-
#
|
117
|
-
#
|
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
|
-
#
|
120
|
-
#
|
121
|
-
#
|
122
|
-
#
|
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
|
-
#
|
125
|
-
#
|
127
|
+
# # Sets a "permanent" cookie (which expires in 20 years from now).
|
128
|
+
# cookies.permanent[:login] = "XJ-122"
|
126
129
|
#
|
127
|
-
#
|
128
|
-
#
|
130
|
+
# # You can also chain these methods:
|
131
|
+
# cookies.signed.permanent[:login] = "XJ-122"
|
129
132
|
#
|
130
133
|
# Examples of reading:
|
131
134
|
#
|
132
|
-
#
|
133
|
-
#
|
134
|
-
#
|
135
|
-
#
|
136
|
-
#
|
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
|
-
#
|
143
|
+
# cookies.delete :user_name
|
141
144
|
#
|
142
|
-
# Please note that if you specify a
|
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
|
-
#
|
145
|
-
#
|
146
|
-
#
|
147
|
-
#
|
148
|
-
#
|
148
|
+
# cookies[:name] = {
|
149
|
+
# value: 'a yummy cookie',
|
150
|
+
# expires: 1.year,
|
151
|
+
# domain: 'domain.com'
|
152
|
+
# }
|
149
153
|
#
|
150
|
-
#
|
154
|
+
# cookies.delete(:name, domain: 'domain.com')
|
151
155
|
#
|
152
156
|
# The option symbols for setting cookies are:
|
153
157
|
#
|
154
|
-
# *
|
155
|
-
# *
|
156
|
-
#
|
157
|
-
# *
|
158
|
-
#
|
159
|
-
#
|
160
|
-
#
|
161
|
-
#
|
162
|
-
#
|
163
|
-
#
|
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
|
-
#
|
166
|
-
#
|
167
|
-
#
|
168
|
-
#
|
169
|
-
#
|
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.
|
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
|
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
|
-
#
|
210
|
-
#
|
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
|
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
|
227
|
+
# This jar allows chaining with the signed jar as well, so you can set
|
228
|
+
# permanent, signed cookies. Examples:
|
215
229
|
#
|
216
|
-
#
|
217
|
-
#
|
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
|
223
|
-
# the cookie again. This is useful for
|
224
|
-
#
|
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
|
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
|
-
#
|
231
|
-
#
|
247
|
+
# cookies.signed[:discount] = 45
|
248
|
+
# # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
|
232
249
|
#
|
233
|
-
#
|
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
|
239
|
-
#
|
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
|
242
|
-
# are both set, legacy
|
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
|
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
|
-
#
|
249
|
-
#
|
268
|
+
# cookies.encrypted[:discount] = 45
|
269
|
+
# # => Set-Cookie: discount=DIQ7fw==--K3n//8vvnSbGq9dA--7Xh91HfLpwzbj1czhBiwOg==; path=/
|
250
270
|
#
|
251
|
-
#
|
271
|
+
# cookies.encrypted[:discount] # => 45
|
252
272
|
def encrypted
|
253
273
|
@encrypted ||= EncryptedKeyRotatingCookieJar.new(self)
|
254
274
|
end
|
255
275
|
|
256
|
-
# Returns the
|
257
|
-
# Used by ActionDispatch::Session::CookieStore to
|
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
|
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
|
355
|
-
#
|
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
|
377
|
-
# and the expiration date in the past. Like
|
378
|
-
#
|
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
|
-
#
|
392
|
-
#
|
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
|
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(
|
405
|
-
|
406
|
-
|
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
|
-
|
442
|
-
|
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
|
-
#
|
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
|
-
|
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
|
572
|
-
request.
|
567
|
+
def digest
|
568
|
+
request.cookies_digest || "SHA1"
|
573
569
|
end
|
574
570
|
|
575
|
-
|
576
|
-
|
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
|
580
|
-
|
581
|
-
|
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
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
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
|
600
|
-
|
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
|
612
|
-
|
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
|
-
|
634
|
-
|
635
|
-
|
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
|
-
|
640
|
-
|
641
|
-
|
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
|
-
|
684
|
-
|
685
|
-
|
686
|
-
rescue ActiveSupport::MessageEncryptor::InvalidMessage, ActiveSupport::MessageVerifier::InvalidSignature
|
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
|
-
|
692
|
-
|
693
|
-
|
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
|
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
|
-
|
710
|
-
|
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
|
-
|
714
|
+
response.to_a
|
717
715
|
end
|
718
716
|
end
|
719
717
|
end
|