actionpack 5.2.8.1 → 6.1.7.2
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 +410 -343
- data/MIT-LICENSE +1 -2
- data/README.rdoc +4 -3
- data/lib/abstract_controller/base.rb +38 -4
- data/lib/abstract_controller/caching/fragments.rb +6 -22
- data/lib/abstract_controller/caching.rb +1 -1
- data/lib/abstract_controller/callbacks.rb +14 -2
- data/lib/abstract_controller/collector.rb +5 -4
- data/lib/abstract_controller/helpers.rb +106 -90
- data/lib/abstract_controller/railties/routes_helpers.rb +17 -1
- data/lib/abstract_controller/rendering.rb +9 -9
- data/lib/abstract_controller/translation.rb +11 -5
- data/lib/abstract_controller.rb +1 -0
- data/lib/action_controller/api.rb +4 -3
- data/lib/action_controller/base.rb +6 -9
- data/lib/action_controller/caching.rb +1 -3
- data/lib/action_controller/log_subscriber.rb +10 -7
- data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
- data/lib/action_controller/metal/conditional_get.rb +19 -5
- data/lib/action_controller/metal/content_security_policy.rb +1 -2
- data/lib/action_controller/metal/cookies.rb +3 -1
- data/lib/action_controller/metal/data_streaming.rb +6 -7
- data/lib/action_controller/metal/default_headers.rb +17 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +4 -6
- data/lib/action_controller/metal/exceptions.rb +56 -2
- data/lib/action_controller/metal/flash.rb +5 -5
- data/lib/action_controller/metal/head.rb +7 -4
- data/lib/action_controller/metal/helpers.rb +14 -5
- data/lib/action_controller/metal/http_authentication.rb +25 -23
- data/lib/action_controller/metal/implicit_render.rb +5 -15
- data/lib/action_controller/metal/instrumentation.rb +13 -14
- data/lib/action_controller/metal/live.rb +39 -32
- data/lib/action_controller/metal/logging.rb +20 -0
- data/lib/action_controller/metal/mime_responds.rb +19 -4
- data/lib/action_controller/metal/parameter_encoding.rb +35 -4
- data/lib/action_controller/metal/params_wrapper.rb +32 -22
- data/lib/action_controller/metal/permissions_policy.rb +46 -0
- data/lib/action_controller/metal/redirecting.rb +6 -6
- data/lib/action_controller/metal/renderers.rb +4 -4
- data/lib/action_controller/metal/rendering.rb +8 -3
- data/lib/action_controller/metal/request_forgery_protection.rb +26 -49
- data/lib/action_controller/metal/rescue.rb +1 -1
- data/lib/action_controller/metal/streaming.rb +0 -1
- data/lib/action_controller/metal/strong_parameters.rb +168 -59
- data/lib/action_controller/metal/url_for.rb +1 -1
- data/lib/action_controller/metal.rb +10 -8
- data/lib/action_controller/railties/helpers.rb +1 -1
- data/lib/action_controller/renderer.rb +37 -13
- data/lib/action_controller/template_assertions.rb +1 -1
- data/lib/action_controller/test_case.rb +71 -63
- data/lib/action_controller.rb +7 -4
- data/lib/action_dispatch/http/cache.rb +32 -28
- data/lib/action_dispatch/http/content_disposition.rb +45 -0
- data/lib/action_dispatch/http/content_security_policy.rb +34 -18
- data/lib/action_dispatch/http/filter_parameters.rb +9 -8
- data/lib/action_dispatch/http/filter_redirect.rb +2 -3
- data/lib/action_dispatch/http/headers.rb +4 -4
- data/lib/action_dispatch/http/mime_negotiation.rb +26 -13
- data/lib/action_dispatch/http/mime_type.rb +43 -24
- data/lib/action_dispatch/http/parameters.rb +14 -23
- data/lib/action_dispatch/http/permissions_policy.rb +173 -0
- data/lib/action_dispatch/http/request.rb +45 -22
- data/lib/action_dispatch/http/response.rb +45 -25
- data/lib/action_dispatch/http/upload.rb +9 -1
- data/lib/action_dispatch/http/url.rb +82 -82
- data/lib/action_dispatch/journey/formatter.rb +55 -31
- data/lib/action_dispatch/journey/gtg/builder.rb +22 -37
- data/lib/action_dispatch/journey/gtg/simulator.rb +8 -7
- data/lib/action_dispatch/journey/gtg/transition_table.rb +6 -5
- data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
- data/lib/action_dispatch/journey/nodes/node.rb +13 -11
- data/lib/action_dispatch/journey/parser.rb +13 -13
- data/lib/action_dispatch/journey/parser.y +1 -1
- data/lib/action_dispatch/journey/path/pattern.rb +19 -21
- data/lib/action_dispatch/journey/route.rb +10 -20
- data/lib/action_dispatch/journey/router/utils.rb +14 -12
- data/lib/action_dispatch/journey/router.rb +26 -34
- data/lib/action_dispatch/journey/routes.rb +0 -2
- data/lib/action_dispatch/journey/scanner.rb +10 -4
- data/lib/action_dispatch/journey/visitors.rb +1 -4
- data/lib/action_dispatch/journey.rb +0 -2
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
- data/lib/action_dispatch/middleware/callbacks.rb +2 -4
- data/lib/action_dispatch/middleware/cookies.rb +150 -123
- data/lib/action_dispatch/middleware/debug_exceptions.rb +43 -66
- data/lib/action_dispatch/middleware/debug_locks.rb +5 -5
- data/lib/action_dispatch/middleware/debug_view.rb +66 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +75 -30
- data/lib/action_dispatch/middleware/flash.rb +1 -1
- data/lib/action_dispatch/middleware/host_authorization.rb +170 -0
- data/lib/action_dispatch/middleware/public_exceptions.rb +6 -3
- data/lib/action_dispatch/middleware/remote_ip.rb +14 -16
- data/lib/action_dispatch/middleware/request_id.rb +5 -6
- data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -3
- data/lib/action_dispatch/middleware/session/cookie_store.rb +3 -9
- data/lib/action_dispatch/middleware/show_exceptions.rb +13 -2
- data/lib/action_dispatch/middleware/ssl.rb +20 -15
- data/lib/action_dispatch/middleware/stack.rb +56 -2
- data/lib/action_dispatch/middleware/static.rb +153 -93
- data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
- data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +3 -1
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +4 -2
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +45 -35
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -0
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +23 -4
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +6 -3
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +4 -1
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +104 -8
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +24 -1
- data/lib/action_dispatch/railtie.rb +8 -2
- data/lib/action_dispatch/request/session.rb +11 -10
- data/lib/action_dispatch/request/utils.rb +26 -2
- data/lib/action_dispatch/routing/inspector.rb +100 -52
- data/lib/action_dispatch/routing/mapper.rb +155 -103
- data/lib/action_dispatch/routing/polymorphic_routes.rb +13 -15
- data/lib/action_dispatch/routing/redirection.rb +4 -4
- data/lib/action_dispatch/routing/route_set.rb +71 -69
- data/lib/action_dispatch/routing/url_for.rb +2 -2
- data/lib/action_dispatch/routing.rb +21 -20
- data/lib/action_dispatch/system_test_case.rb +60 -11
- data/lib/action_dispatch/system_testing/browser.rb +53 -16
- data/lib/action_dispatch/system_testing/driver.rb +11 -3
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +49 -7
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +8 -10
- data/lib/action_dispatch/testing/assertion_response.rb +0 -1
- data/lib/action_dispatch/testing/assertions/response.rb +4 -7
- data/lib/action_dispatch/testing/assertions/routing.rb +20 -8
- data/lib/action_dispatch/testing/assertions.rb +1 -1
- data/lib/action_dispatch/testing/integration.rb +60 -28
- data/lib/action_dispatch/testing/request_encoder.rb +2 -2
- data/lib/action_dispatch/testing/test_process.rb +32 -4
- data/lib/action_dispatch/testing/test_request.rb +3 -3
- data/lib/action_dispatch/testing/test_response.rb +4 -32
- data/lib/action_dispatch.rb +9 -3
- data/lib/action_pack/gem_version.rb +4 -4
- data/lib/action_pack.rb +1 -1
- metadata +36 -23
- data/lib/action_controller/metal/force_ssl.rb +0 -99
- data/lib/action_dispatch/http/parameter_filter.rb +0 -86
- data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
- data/lib/action_dispatch/journey/nfa/simulator.rb +0 -49
- data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -120
- data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +0 -26
|
@@ -9,7 +9,7 @@ require "rack/utils"
|
|
|
9
9
|
module ActionDispatch
|
|
10
10
|
class Request
|
|
11
11
|
def cookie_jar
|
|
12
|
-
fetch_header("action_dispatch.cookies"
|
|
12
|
+
fetch_header("action_dispatch.cookies") do
|
|
13
13
|
self.cookie_jar = Cookies::CookieJar.build(self, cookies)
|
|
14
14
|
end
|
|
15
15
|
end
|
|
@@ -22,11 +22,11 @@ module ActionDispatch
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
def have_cookie_jar?
|
|
25
|
-
has_header? "action_dispatch.cookies"
|
|
25
|
+
has_header? "action_dispatch.cookies"
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def cookie_jar=(jar)
|
|
29
|
-
set_header "action_dispatch.cookies"
|
|
29
|
+
set_header "action_dispatch.cookies", jar
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def key_generator
|
|
@@ -61,10 +61,6 @@ module ActionDispatch
|
|
|
61
61
|
get_header Cookies::SIGNED_COOKIE_DIGEST
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
-
def secret_token
|
|
65
|
-
get_header Cookies::SECRET_TOKEN
|
|
66
|
-
end
|
|
67
|
-
|
|
68
64
|
def secret_key_base
|
|
69
65
|
get_header Cookies::SECRET_KEY_BASE
|
|
70
66
|
end
|
|
@@ -73,6 +69,10 @@ module ActionDispatch
|
|
|
73
69
|
get_header Cookies::COOKIES_SERIALIZER
|
|
74
70
|
end
|
|
75
71
|
|
|
72
|
+
def cookies_same_site_protection
|
|
73
|
+
get_header(Cookies::COOKIES_SAME_SITE_PROTECTION) || Proc.new { }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
76
|
def cookies_digest
|
|
77
77
|
get_header Cookies::COOKIES_DIGEST
|
|
78
78
|
end
|
|
@@ -81,14 +81,17 @@ module ActionDispatch
|
|
|
81
81
|
get_header Cookies::COOKIES_ROTATIONS
|
|
82
82
|
end
|
|
83
83
|
|
|
84
|
+
def use_cookies_with_metadata
|
|
85
|
+
get_header Cookies::USE_COOKIES_WITH_METADATA
|
|
86
|
+
end
|
|
87
|
+
|
|
84
88
|
# :startdoc:
|
|
85
89
|
end
|
|
86
90
|
|
|
87
|
-
#
|
|
91
|
+
# Read and write data to cookies through ActionController#cookies.
|
|
88
92
|
#
|
|
89
|
-
#
|
|
90
|
-
#
|
|
91
|
-
# the cookie object itself back, just the value it holds.
|
|
93
|
+
# 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.
|
|
92
95
|
#
|
|
93
96
|
# Examples of writing:
|
|
94
97
|
#
|
|
@@ -150,8 +153,10 @@ module ActionDispatch
|
|
|
150
153
|
# * <tt>:domain</tt> - The domain for which this cookie applies so you can
|
|
151
154
|
# restrict to the domain level. If you use a schema like www.example.com
|
|
152
155
|
# and want to share session with user.example.com set <tt>:domain</tt>
|
|
153
|
-
# to <tt>:all</tt>.
|
|
154
|
-
#
|
|
156
|
+
# to <tt>:all</tt>. To support multiple domains, provide an array, and
|
|
157
|
+
# the first domain matching <tt>request.host</tt> will be used. Make
|
|
158
|
+
# sure to specify the <tt>:domain</tt> option with <tt>:all</tt> or
|
|
159
|
+
# <tt>Array</tt> again when deleting cookies.
|
|
155
160
|
#
|
|
156
161
|
# domain: nil # Does not set cookie domain. (default)
|
|
157
162
|
# domain: :all # Allow the cookie for the top most level
|
|
@@ -168,20 +173,21 @@ module ActionDispatch
|
|
|
168
173
|
# * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
|
|
169
174
|
# only HTTP. Defaults to +false+.
|
|
170
175
|
class Cookies
|
|
171
|
-
HTTP_HEADER = "Set-Cookie"
|
|
172
|
-
GENERATOR_KEY = "action_dispatch.key_generator"
|
|
173
|
-
SIGNED_COOKIE_SALT = "action_dispatch.signed_cookie_salt"
|
|
174
|
-
ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt"
|
|
175
|
-
ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt"
|
|
176
|
-
AUTHENTICATED_ENCRYPTED_COOKIE_SALT = "action_dispatch.authenticated_encrypted_cookie_salt"
|
|
177
|
-
USE_AUTHENTICATED_COOKIE_ENCRYPTION = "action_dispatch.use_authenticated_cookie_encryption"
|
|
178
|
-
ENCRYPTED_COOKIE_CIPHER = "action_dispatch.encrypted_cookie_cipher"
|
|
179
|
-
SIGNED_COOKIE_DIGEST = "action_dispatch.signed_cookie_digest"
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
176
|
+
HTTP_HEADER = "Set-Cookie"
|
|
177
|
+
GENERATOR_KEY = "action_dispatch.key_generator"
|
|
178
|
+
SIGNED_COOKIE_SALT = "action_dispatch.signed_cookie_salt"
|
|
179
|
+
ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt"
|
|
180
|
+
ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt"
|
|
181
|
+
AUTHENTICATED_ENCRYPTED_COOKIE_SALT = "action_dispatch.authenticated_encrypted_cookie_salt"
|
|
182
|
+
USE_AUTHENTICATED_COOKIE_ENCRYPTION = "action_dispatch.use_authenticated_cookie_encryption"
|
|
183
|
+
ENCRYPTED_COOKIE_CIPHER = "action_dispatch.encrypted_cookie_cipher"
|
|
184
|
+
SIGNED_COOKIE_DIGEST = "action_dispatch.signed_cookie_digest"
|
|
185
|
+
SECRET_KEY_BASE = "action_dispatch.secret_key_base"
|
|
186
|
+
COOKIES_SERIALIZER = "action_dispatch.cookies_serializer"
|
|
187
|
+
COOKIES_DIGEST = "action_dispatch.cookies_digest"
|
|
188
|
+
COOKIES_ROTATIONS = "action_dispatch.cookies_rotations"
|
|
189
|
+
COOKIES_SAME_SITE_PROTECTION = "action_dispatch.cookies_same_site_protection"
|
|
190
|
+
USE_COOKIES_WITH_METADATA = "action_dispatch.use_cookies_with_metadata"
|
|
185
191
|
|
|
186
192
|
# Cookies can typically store 4096 bytes.
|
|
187
193
|
MAX_COOKIE_SIZE = 4096
|
|
@@ -210,9 +216,6 @@ module ActionDispatch
|
|
|
210
216
|
# the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
|
|
211
217
|
# cookie was tampered with by the user (or a 3rd party), +nil+ will be returned.
|
|
212
218
|
#
|
|
213
|
-
# If +secret_key_base+ and +secrets.secret_token+ (deprecated) are both set,
|
|
214
|
-
# legacy cookies signed with the old key generator will be transparently upgraded.
|
|
215
|
-
#
|
|
216
219
|
# This jar requires that you set a suitable secret for the verification on your app's +secret_key_base+.
|
|
217
220
|
#
|
|
218
221
|
# Example:
|
|
@@ -228,9 +231,6 @@ module ActionDispatch
|
|
|
228
231
|
# Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
|
|
229
232
|
# If the cookie was tampered with by the user (or a 3rd party), +nil+ will be returned.
|
|
230
233
|
#
|
|
231
|
-
# If +secret_key_base+ and +secrets.secret_token+ (deprecated) are both set,
|
|
232
|
-
# legacy cookies signed with the old key generator will be transparently upgraded.
|
|
233
|
-
#
|
|
234
234
|
# If +config.action_dispatch.encrypted_cookie_salt+ and +config.action_dispatch.encrypted_signed_cookie_salt+
|
|
235
235
|
# are both set, legacy cookies encrypted with HMAC AES-256-CBC will be transparently upgraded.
|
|
236
236
|
#
|
|
@@ -258,11 +258,6 @@ module ActionDispatch
|
|
|
258
258
|
end
|
|
259
259
|
|
|
260
260
|
private
|
|
261
|
-
|
|
262
|
-
def upgrade_legacy_signed_cookies?
|
|
263
|
-
request.secret_token.present? && request.secret_key_base.present?
|
|
264
|
-
end
|
|
265
|
-
|
|
266
261
|
def upgrade_legacy_hmac_aes_cbc_cookies?
|
|
267
262
|
request.secret_key_base.present? &&
|
|
268
263
|
request.encrypted_signed_cookie_salt.present? &&
|
|
@@ -270,6 +265,12 @@ module ActionDispatch
|
|
|
270
265
|
request.use_authenticated_cookie_encryption
|
|
271
266
|
end
|
|
272
267
|
|
|
268
|
+
def prepare_upgrade_legacy_hmac_aes_cbc_cookies?
|
|
269
|
+
request.secret_key_base.present? &&
|
|
270
|
+
request.authenticated_encrypted_cookie_salt.present? &&
|
|
271
|
+
!request.use_authenticated_cookie_encryption
|
|
272
|
+
end
|
|
273
|
+
|
|
273
274
|
def encrypted_cookie_cipher
|
|
274
275
|
request.encrypted_cookie_cipher || "aes-256-gcm"
|
|
275
276
|
end
|
|
@@ -282,24 +283,10 @@ module ActionDispatch
|
|
|
282
283
|
class CookieJar #:nodoc:
|
|
283
284
|
include Enumerable, ChainedCookieJars
|
|
284
285
|
|
|
285
|
-
# This regular expression is used to split the levels of a domain.
|
|
286
|
-
# The top level domain can be any string without a period or
|
|
287
|
-
# **.**, ***.** style TLDs like co.uk or com.au
|
|
288
|
-
#
|
|
289
|
-
# www.example.co.uk gives:
|
|
290
|
-
# $& => example.co.uk
|
|
291
|
-
#
|
|
292
|
-
# example.com gives:
|
|
293
|
-
# $& => example.com
|
|
294
|
-
#
|
|
295
|
-
# lots.of.subdomains.example.local gives:
|
|
296
|
-
# $& => example.local
|
|
297
|
-
DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/
|
|
298
|
-
|
|
299
286
|
def self.build(req, cookies)
|
|
300
|
-
new(req)
|
|
301
|
-
|
|
302
|
-
|
|
287
|
+
jar = new(req)
|
|
288
|
+
jar.update(cookies)
|
|
289
|
+
jar
|
|
303
290
|
end
|
|
304
291
|
|
|
305
292
|
attr_reader :request
|
|
@@ -348,7 +335,7 @@ module ActionDispatch
|
|
|
348
335
|
|
|
349
336
|
def update_cookies_from_jar
|
|
350
337
|
request_jar = @request.cookie_jar.instance_variable_get(:@cookies)
|
|
351
|
-
set_cookies = request_jar.reject { |k, _| @delete_cookies.key?(k) }
|
|
338
|
+
set_cookies = request_jar.reject { |k, _| @delete_cookies.key?(k) || @set_cookies.key?(k) }
|
|
352
339
|
|
|
353
340
|
@cookies.update set_cookies if set_cookies
|
|
354
341
|
end
|
|
@@ -357,28 +344,6 @@ module ActionDispatch
|
|
|
357
344
|
@cookies.map { |k, v| "#{escape(k)}=#{escape(v)}" }.join "; "
|
|
358
345
|
end
|
|
359
346
|
|
|
360
|
-
def handle_options(options) # :nodoc:
|
|
361
|
-
if options[:expires].respond_to?(:from_now)
|
|
362
|
-
options[:expires] = options[:expires].from_now
|
|
363
|
-
end
|
|
364
|
-
|
|
365
|
-
options[:path] ||= "/"
|
|
366
|
-
|
|
367
|
-
if options[:domain] == :all || options[:domain] == "all"
|
|
368
|
-
# If there is a provided tld length then we use it otherwise default domain regexp.
|
|
369
|
-
domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP
|
|
370
|
-
|
|
371
|
-
# If host is not ip and matches domain regexp.
|
|
372
|
-
# (ip confirms to domain regexp so we explicitly check for ip)
|
|
373
|
-
options[:domain] = if (request.host !~ /^[\d.]+$/) && (request.host =~ domain_regexp)
|
|
374
|
-
".#{$&}"
|
|
375
|
-
end
|
|
376
|
-
elsif options[:domain].is_a? Array
|
|
377
|
-
# If host matches one of the supplied domains without a dot in front of it.
|
|
378
|
-
options[:domain] = options[:domain].find { |domain| request.host.include? domain.sub(/^\./, "") }
|
|
379
|
-
end
|
|
380
|
-
end
|
|
381
|
-
|
|
382
347
|
# Sets the cookie named +name+. The second argument may be the cookie's
|
|
383
348
|
# value or a hash of options as documented above.
|
|
384
349
|
def []=(name, options)
|
|
@@ -438,7 +403,6 @@ module ActionDispatch
|
|
|
438
403
|
mattr_accessor :always_write_cookie, default: false
|
|
439
404
|
|
|
440
405
|
private
|
|
441
|
-
|
|
442
406
|
def escape(string)
|
|
443
407
|
::Rack::Utils.escape(string)
|
|
444
408
|
end
|
|
@@ -459,6 +423,56 @@ module ActionDispatch
|
|
|
459
423
|
def write_cookie?(cookie)
|
|
460
424
|
request.ssl? || !cookie[:secure] || always_write_cookie
|
|
461
425
|
end
|
|
426
|
+
|
|
427
|
+
def handle_options(options)
|
|
428
|
+
if options[:expires].respond_to?(:from_now)
|
|
429
|
+
options[:expires] = options[:expires].from_now
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
options[:path] ||= "/"
|
|
433
|
+
|
|
434
|
+
cookies_same_site_protection = request.cookies_same_site_protection
|
|
435
|
+
options[:same_site] ||= cookies_same_site_protection.call(request)
|
|
436
|
+
|
|
437
|
+
if options[:domain] == :all || options[:domain] == "all"
|
|
438
|
+
cookie_domain = ""
|
|
439
|
+
dot_splitted_host = request.host.split('.', -1)
|
|
440
|
+
|
|
441
|
+
# Case where request.host is not an IP address or it's an invalid domain
|
|
442
|
+
# (ip confirms to the domain structure we expect so we explicitly check for ip)
|
|
443
|
+
if request.host.match?(/^[\d.]+$/) || dot_splitted_host.include?("") || dot_splitted_host.length == 1
|
|
444
|
+
options[:domain] = nil
|
|
445
|
+
return
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
# If there is a provided tld length then we use it otherwise default domain.
|
|
449
|
+
if options[:tld_length].present?
|
|
450
|
+
# Case where the tld_length provided is valid
|
|
451
|
+
if dot_splitted_host.length >= options[:tld_length]
|
|
452
|
+
cookie_domain = dot_splitted_host.last(options[:tld_length]).join('.')
|
|
453
|
+
end
|
|
454
|
+
# Case where tld_length is not provided
|
|
455
|
+
else
|
|
456
|
+
# Regular TLDs
|
|
457
|
+
if !(/\.[^.]{2,3}\.[^.]{2}\z/.match?(request.host))
|
|
458
|
+
cookie_domain = dot_splitted_host.last(2).join(".")
|
|
459
|
+
# **.**, ***.** style TLDs like co.uk and com.au
|
|
460
|
+
else
|
|
461
|
+
cookie_domain = dot_splitted_host.last(3).join('.')
|
|
462
|
+
end
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
options[:domain] = if cookie_domain.present?
|
|
466
|
+
".#{cookie_domain}"
|
|
467
|
+
end
|
|
468
|
+
elsif options[:domain].is_a? Array
|
|
469
|
+
# If host matches one of the supplied domains.
|
|
470
|
+
options[:domain] = options[:domain].find do |domain|
|
|
471
|
+
domain = domain.delete_prefix(".")
|
|
472
|
+
request.host == domain || request.host.end_with?(".#{domain}")
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
end
|
|
462
476
|
end
|
|
463
477
|
|
|
464
478
|
class AbstractCookieJar # :nodoc:
|
|
@@ -470,7 +484,13 @@ module ActionDispatch
|
|
|
470
484
|
|
|
471
485
|
def [](name)
|
|
472
486
|
if data = @parent_jar[name.to_s]
|
|
473
|
-
parse
|
|
487
|
+
result = parse(name, data, purpose: "cookie.#{name}")
|
|
488
|
+
|
|
489
|
+
if result.nil?
|
|
490
|
+
parse(name, data)
|
|
491
|
+
else
|
|
492
|
+
result
|
|
493
|
+
end
|
|
474
494
|
end
|
|
475
495
|
end
|
|
476
496
|
|
|
@@ -481,7 +501,7 @@ module ActionDispatch
|
|
|
481
501
|
options = { value: options }
|
|
482
502
|
end
|
|
483
503
|
|
|
484
|
-
commit(options)
|
|
504
|
+
commit(name, options)
|
|
485
505
|
@parent_jar[name] = options
|
|
486
506
|
end
|
|
487
507
|
|
|
@@ -490,28 +510,42 @@ module ActionDispatch
|
|
|
490
510
|
|
|
491
511
|
private
|
|
492
512
|
def expiry_options(options)
|
|
493
|
-
if
|
|
494
|
-
|
|
495
|
-
{ expires_in: options[:expires] }
|
|
496
|
-
else
|
|
497
|
-
{ expires_at: options[:expires] }
|
|
498
|
-
end
|
|
513
|
+
if options[:expires].respond_to?(:from_now)
|
|
514
|
+
{ expires_in: options[:expires] }
|
|
499
515
|
else
|
|
500
|
-
{}
|
|
516
|
+
{ expires_at: options[:expires] }
|
|
501
517
|
end
|
|
502
518
|
end
|
|
503
519
|
|
|
504
|
-
def
|
|
505
|
-
|
|
520
|
+
def cookie_metadata(name, options)
|
|
521
|
+
expiry_options(options).tap do |metadata|
|
|
522
|
+
metadata[:purpose] = "cookie.#{name}" if request.use_cookies_with_metadata
|
|
523
|
+
end
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
def parse(name, data, purpose: nil); data; end
|
|
527
|
+
def commit(name, options); end
|
|
506
528
|
end
|
|
507
529
|
|
|
508
530
|
class PermanentCookieJar < AbstractCookieJar # :nodoc:
|
|
509
531
|
private
|
|
510
|
-
def commit(options)
|
|
532
|
+
def commit(name, options)
|
|
511
533
|
options[:expires] = 20.years.from_now
|
|
512
534
|
end
|
|
513
535
|
end
|
|
514
536
|
|
|
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
|
+
|
|
515
549
|
class JsonSerializer # :nodoc:
|
|
516
550
|
def self.load(value)
|
|
517
551
|
ActiveSupport::JSON.decode(value)
|
|
@@ -523,7 +557,7 @@ module ActionDispatch
|
|
|
523
557
|
end
|
|
524
558
|
|
|
525
559
|
module SerializedCookieJars # :nodoc:
|
|
526
|
-
MARSHAL_SIGNATURE = "\x04\x08"
|
|
560
|
+
MARSHAL_SIGNATURE = "\x04\x08"
|
|
527
561
|
SERIALIZER = ActiveSupport::MessageEncryptor::NullSerializer
|
|
528
562
|
|
|
529
563
|
protected
|
|
@@ -542,9 +576,13 @@ module ActionDispatch
|
|
|
542
576
|
if value
|
|
543
577
|
case
|
|
544
578
|
when needs_migration?(value)
|
|
545
|
-
|
|
579
|
+
Marshal.load(value).tap do |v|
|
|
580
|
+
self[name] = { value: v }
|
|
581
|
+
end
|
|
546
582
|
when rotate
|
|
547
|
-
|
|
583
|
+
serializer.load(value).tap do |v|
|
|
584
|
+
self[name] = { value: v }
|
|
585
|
+
end
|
|
548
586
|
else
|
|
549
587
|
serializer.load(value)
|
|
550
588
|
end
|
|
@@ -555,7 +593,7 @@ module ActionDispatch
|
|
|
555
593
|
serializer = request.cookies_serializer || :marshal
|
|
556
594
|
case serializer
|
|
557
595
|
when :marshal
|
|
558
|
-
|
|
596
|
+
MarshalWithJsonFallback
|
|
559
597
|
when :json, :hybrid
|
|
560
598
|
JsonSerializer
|
|
561
599
|
else
|
|
@@ -577,24 +615,21 @@ module ActionDispatch
|
|
|
577
615
|
secret = request.key_generator.generate_key(request.signed_cookie_salt)
|
|
578
616
|
@verifier = ActiveSupport::MessageVerifier.new(secret, digest: signed_cookie_digest, serializer: SERIALIZER)
|
|
579
617
|
|
|
580
|
-
request.cookies_rotations.signed.each do
|
|
618
|
+
request.cookies_rotations.signed.each do |(*secrets)|
|
|
619
|
+
options = secrets.extract_options!
|
|
581
620
|
@verifier.rotate(*secrets, serializer: SERIALIZER, **options)
|
|
582
621
|
end
|
|
583
|
-
|
|
584
|
-
if upgrade_legacy_signed_cookies?
|
|
585
|
-
@verifier.rotate request.secret_token, serializer: SERIALIZER
|
|
586
|
-
end
|
|
587
622
|
end
|
|
588
623
|
|
|
589
624
|
private
|
|
590
|
-
def parse(name, signed_message)
|
|
625
|
+
def parse(name, signed_message, purpose: nil)
|
|
591
626
|
deserialize(name) do |rotate|
|
|
592
|
-
@verifier.verified(signed_message, on_rotation: rotate)
|
|
627
|
+
@verifier.verified(signed_message, on_rotation: rotate, purpose: purpose)
|
|
593
628
|
end
|
|
594
629
|
end
|
|
595
630
|
|
|
596
|
-
def commit(options)
|
|
597
|
-
options[:value] = @verifier.generate(serialize(options[:value]),
|
|
631
|
+
def commit(name, options)
|
|
632
|
+
options[:value] = @verifier.generate(serialize(options[:value]), **cookie_metadata(name, options))
|
|
598
633
|
|
|
599
634
|
raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
|
|
600
635
|
end
|
|
@@ -617,7 +652,8 @@ module ActionDispatch
|
|
|
617
652
|
@encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, cipher: "aes-256-cbc", serializer: SERIALIZER)
|
|
618
653
|
end
|
|
619
654
|
|
|
620
|
-
request.cookies_rotations.encrypted.each do
|
|
655
|
+
request.cookies_rotations.encrypted.each do |(*secrets)|
|
|
656
|
+
options = secrets.extract_options!
|
|
621
657
|
@encryptor.rotate(*secrets, serializer: SERIALIZER, **options)
|
|
622
658
|
end
|
|
623
659
|
|
|
@@ -627,37 +663,28 @@ module ActionDispatch
|
|
|
627
663
|
sign_secret = request.key_generator.generate_key(request.encrypted_signed_cookie_salt)
|
|
628
664
|
|
|
629
665
|
@encryptor.rotate(secret, sign_secret, cipher: legacy_cipher, digest: digest, serializer: SERIALIZER)
|
|
630
|
-
|
|
666
|
+
elsif prepare_upgrade_legacy_hmac_aes_cbc_cookies?
|
|
667
|
+
future_cipher = encrypted_cookie_cipher
|
|
668
|
+
secret = request.key_generator.generate_key(request.authenticated_encrypted_cookie_salt, ActiveSupport::MessageEncryptor.key_len(future_cipher))
|
|
631
669
|
|
|
632
|
-
|
|
633
|
-
@legacy_verifier = ActiveSupport::MessageVerifier.new(request.secret_token, digest: digest, serializer: SERIALIZER)
|
|
670
|
+
@encryptor.rotate(secret, nil, cipher: future_cipher, serializer: SERIALIZER)
|
|
634
671
|
end
|
|
635
672
|
end
|
|
636
673
|
|
|
637
674
|
private
|
|
638
|
-
def parse(name, encrypted_message)
|
|
675
|
+
def parse(name, encrypted_message, purpose: nil)
|
|
639
676
|
deserialize(name) do |rotate|
|
|
640
|
-
@encryptor.decrypt_and_verify(encrypted_message, on_rotation: rotate)
|
|
677
|
+
@encryptor.decrypt_and_verify(encrypted_message, on_rotation: rotate, purpose: purpose)
|
|
641
678
|
end
|
|
642
679
|
rescue ActiveSupport::MessageEncryptor::InvalidMessage, ActiveSupport::MessageVerifier::InvalidSignature
|
|
643
|
-
|
|
680
|
+
nil
|
|
644
681
|
end
|
|
645
682
|
|
|
646
|
-
def commit(options)
|
|
647
|
-
options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value]),
|
|
683
|
+
def commit(name, options)
|
|
684
|
+
options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value]), **cookie_metadata(name, options))
|
|
648
685
|
|
|
649
686
|
raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
|
|
650
687
|
end
|
|
651
|
-
|
|
652
|
-
def parse_legacy_signed_message(name, legacy_signed_message)
|
|
653
|
-
if defined?(@legacy_verifier)
|
|
654
|
-
deserialize(name) do |rotate|
|
|
655
|
-
rotate.call
|
|
656
|
-
|
|
657
|
-
@legacy_verifier.verified(legacy_signed_message)
|
|
658
|
-
end
|
|
659
|
-
end
|
|
660
|
-
end
|
|
661
688
|
end
|
|
662
689
|
|
|
663
690
|
def initialize(app)
|
|
@@ -3,57 +3,25 @@
|
|
|
3
3
|
require "action_dispatch/http/request"
|
|
4
4
|
require "action_dispatch/middleware/exception_wrapper"
|
|
5
5
|
require "action_dispatch/routing/inspector"
|
|
6
|
-
require "action_view"
|
|
7
|
-
require "action_view/base"
|
|
8
6
|
|
|
9
|
-
require "
|
|
7
|
+
require "action_view"
|
|
10
8
|
|
|
11
9
|
module ActionDispatch
|
|
12
10
|
# This middleware is responsible for logging exceptions and
|
|
13
11
|
# showing a debugging page in case the request is local.
|
|
14
12
|
class DebugExceptions
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class DebugView < ActionView::Base
|
|
18
|
-
def debug_params(params)
|
|
19
|
-
clean_params = params.clone
|
|
20
|
-
clean_params.delete("action")
|
|
21
|
-
clean_params.delete("controller")
|
|
22
|
-
|
|
23
|
-
if clean_params.empty?
|
|
24
|
-
"None"
|
|
25
|
-
else
|
|
26
|
-
PP.pp(clean_params, "".dup, 200)
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def debug_headers(headers)
|
|
31
|
-
if headers.present?
|
|
32
|
-
headers.inspect.gsub(",", ",\n")
|
|
33
|
-
else
|
|
34
|
-
"None"
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def debug_hash(object)
|
|
39
|
-
object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def render(*)
|
|
43
|
-
logger = ActionView::Base.logger
|
|
13
|
+
cattr_reader :interceptors, instance_accessor: false, default: []
|
|
44
14
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
super
|
|
49
|
-
end
|
|
50
|
-
end
|
|
15
|
+
def self.register_interceptor(object = nil, &block)
|
|
16
|
+
interceptor = object || block
|
|
17
|
+
interceptors << interceptor
|
|
51
18
|
end
|
|
52
19
|
|
|
53
|
-
def initialize(app, routes_app = nil, response_format = :default)
|
|
20
|
+
def initialize(app, routes_app = nil, response_format = :default, interceptors = self.class.interceptors)
|
|
54
21
|
@app = app
|
|
55
22
|
@routes_app = routes_app
|
|
56
23
|
@response_format = response_format
|
|
24
|
+
@interceptors = interceptors
|
|
57
25
|
end
|
|
58
26
|
|
|
59
27
|
def call(env)
|
|
@@ -67,11 +35,22 @@ module ActionDispatch
|
|
|
67
35
|
|
|
68
36
|
response
|
|
69
37
|
rescue Exception => exception
|
|
38
|
+
invoke_interceptors(request, exception)
|
|
70
39
|
raise exception unless request.show_exceptions?
|
|
71
40
|
render_exception(request, exception)
|
|
72
41
|
end
|
|
73
42
|
|
|
74
43
|
private
|
|
44
|
+
def invoke_interceptors(request, exception)
|
|
45
|
+
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
|
|
46
|
+
wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
|
|
47
|
+
|
|
48
|
+
@interceptors.each do |interceptor|
|
|
49
|
+
interceptor.call(request, exception)
|
|
50
|
+
rescue Exception
|
|
51
|
+
log_error(request, wrapper)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
75
54
|
|
|
76
55
|
def render_exception(request, exception)
|
|
77
56
|
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
|
|
@@ -79,7 +58,11 @@ module ActionDispatch
|
|
|
79
58
|
log_error(request, wrapper)
|
|
80
59
|
|
|
81
60
|
if request.get_header("action_dispatch.show_detailed_exceptions")
|
|
82
|
-
|
|
61
|
+
begin
|
|
62
|
+
content_type = request.formats.first
|
|
63
|
+
rescue ActionDispatch::Http::MimeNegotiation::InvalidType
|
|
64
|
+
content_type = Mime[:text]
|
|
65
|
+
end
|
|
83
66
|
|
|
84
67
|
if api_request?(content_type)
|
|
85
68
|
render_for_api_request(content_type, wrapper)
|
|
@@ -130,23 +113,13 @@ module ActionDispatch
|
|
|
130
113
|
end
|
|
131
114
|
|
|
132
115
|
def create_template(request, wrapper)
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
trace_to_show = "Application Trace"
|
|
136
|
-
if traces[trace_to_show].empty? && wrapper.rescue_template != "routing_error"
|
|
137
|
-
trace_to_show = "Full Trace"
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
if source_to_show = traces[trace_to_show].first
|
|
141
|
-
source_to_show_id = source_to_show[:id]
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
DebugView.new([RESCUES_TEMPLATE_PATH],
|
|
116
|
+
DebugView.new(
|
|
145
117
|
request: request,
|
|
118
|
+
exception_wrapper: wrapper,
|
|
146
119
|
exception: wrapper.exception,
|
|
147
|
-
traces: traces,
|
|
148
|
-
show_source_idx: source_to_show_id,
|
|
149
|
-
trace_to_show: trace_to_show,
|
|
120
|
+
traces: wrapper.traces,
|
|
121
|
+
show_source_idx: wrapper.source_to_show_id,
|
|
122
|
+
trace_to_show: wrapper.trace_to_show,
|
|
150
123
|
routes_inspector: routes_inspector(wrapper.exception),
|
|
151
124
|
source_extracts: wrapper.source_extracts,
|
|
152
125
|
line_number: wrapper.line_number,
|
|
@@ -160,27 +133,31 @@ module ActionDispatch
|
|
|
160
133
|
|
|
161
134
|
def log_error(request, wrapper)
|
|
162
135
|
logger = logger(request)
|
|
136
|
+
|
|
163
137
|
return unless logger
|
|
164
138
|
|
|
165
139
|
exception = wrapper.exception
|
|
140
|
+
trace = wrapper.exception_trace
|
|
166
141
|
|
|
167
|
-
|
|
168
|
-
|
|
142
|
+
message = []
|
|
143
|
+
message << " "
|
|
144
|
+
message << "#{exception.class} (#{exception.message}):"
|
|
145
|
+
message.concat(exception.annotated_source_code) if exception.respond_to?(:annotated_source_code)
|
|
146
|
+
message << " "
|
|
147
|
+
message.concat(trace)
|
|
169
148
|
|
|
170
|
-
|
|
171
|
-
logger.fatal " "
|
|
172
|
-
logger.fatal "#{exception.class} (#{exception.message}):"
|
|
173
|
-
log_array logger, exception.annoted_source_code if exception.respond_to?(:annoted_source_code)
|
|
174
|
-
logger.fatal " "
|
|
175
|
-
log_array logger, trace
|
|
176
|
-
end
|
|
149
|
+
log_array(logger, message)
|
|
177
150
|
end
|
|
178
151
|
|
|
179
152
|
def log_array(logger, array)
|
|
153
|
+
lines = Array(array)
|
|
154
|
+
|
|
155
|
+
return if lines.empty?
|
|
156
|
+
|
|
180
157
|
if logger.formatter && logger.formatter.respond_to?(:tags_text)
|
|
181
|
-
logger.fatal
|
|
158
|
+
logger.fatal lines.join("\n#{logger.formatter.tags_text}")
|
|
182
159
|
else
|
|
183
|
-
logger.fatal
|
|
160
|
+
logger.fatal lines.join("\n")
|
|
184
161
|
end
|
|
185
162
|
end
|
|
186
163
|
|
|
@@ -32,7 +32,7 @@ module ActionDispatch
|
|
|
32
32
|
req = ActionDispatch::Request.new env
|
|
33
33
|
|
|
34
34
|
if req.get?
|
|
35
|
-
path = req.path_info.chomp("/"
|
|
35
|
+
path = req.path_info.chomp("/")
|
|
36
36
|
if path == @path
|
|
37
37
|
return render_details(req)
|
|
38
38
|
end
|
|
@@ -63,19 +63,19 @@ module ActionDispatch
|
|
|
63
63
|
|
|
64
64
|
str = threads.map do |thread, info|
|
|
65
65
|
if info[:exclusive]
|
|
66
|
-
lock_state = "Exclusive"
|
|
66
|
+
lock_state = +"Exclusive"
|
|
67
67
|
elsif info[:sharing] > 0
|
|
68
|
-
lock_state = "Sharing"
|
|
68
|
+
lock_state = +"Sharing"
|
|
69
69
|
lock_state << " x#{info[:sharing]}" if info[:sharing] > 1
|
|
70
70
|
else
|
|
71
|
-
lock_state = "No lock"
|
|
71
|
+
lock_state = +"No lock"
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
if info[:waiting]
|
|
75
75
|
lock_state << " (yielded share)"
|
|
76
76
|
end
|
|
77
77
|
|
|
78
|
-
msg = "Thread #{info[:index]} [0x#{thread.__id__.to_s(16)} #{thread.status || 'dead'}] #{lock_state}\n"
|
|
78
|
+
msg = +"Thread #{info[:index]} [0x#{thread.__id__.to_s(16)} #{thread.status || 'dead'}] #{lock_state}\n"
|
|
79
79
|
|
|
80
80
|
if info[:sleeper]
|
|
81
81
|
msg << " Waiting in #{info[:sleeper]}"
|