actionpack 5.2.7.1 → 6.1.4.6
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +329 -352
- data/MIT-LICENSE +1 -1
- 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 +1 -2
- 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 +24 -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 +167 -58
- 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 +31 -27
- data/lib/action_dispatch/http/content_disposition.rb +45 -0
- data/lib/action_dispatch/http/content_security_policy.rb +39 -17
- 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 +128 -109
- 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 +141 -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 +3 -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 +54 -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 +29 -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 +35 -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
|
@@ -297,9 +298,9 @@ module ActionDispatch
|
|
297
298
|
DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/
|
298
299
|
|
299
300
|
def self.build(req, cookies)
|
300
|
-
new(req)
|
301
|
-
|
302
|
-
|
301
|
+
jar = new(req)
|
302
|
+
jar.update(cookies)
|
303
|
+
jar
|
303
304
|
end
|
304
305
|
|
305
306
|
attr_reader :request
|
@@ -348,7 +349,7 @@ module ActionDispatch
|
|
348
349
|
|
349
350
|
def update_cookies_from_jar
|
350
351
|
request_jar = @request.cookie_jar.instance_variable_get(:@cookies)
|
351
|
-
set_cookies = request_jar.reject { |k, _| @delete_cookies.key?(k) }
|
352
|
+
set_cookies = request_jar.reject { |k, _| @delete_cookies.key?(k) || @set_cookies.key?(k) }
|
352
353
|
|
353
354
|
@cookies.update set_cookies if set_cookies
|
354
355
|
end
|
@@ -357,28 +358,6 @@ module ActionDispatch
|
|
357
358
|
@cookies.map { |k, v| "#{escape(k)}=#{escape(v)}" }.join "; "
|
358
359
|
end
|
359
360
|
|
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
361
|
# Sets the cookie named +name+. The second argument may be the cookie's
|
383
362
|
# value or a hash of options as documented above.
|
384
363
|
def []=(name, options)
|
@@ -438,7 +417,6 @@ module ActionDispatch
|
|
438
417
|
mattr_accessor :always_write_cookie, default: false
|
439
418
|
|
440
419
|
private
|
441
|
-
|
442
420
|
def escape(string)
|
443
421
|
::Rack::Utils.escape(string)
|
444
422
|
end
|
@@ -459,6 +437,34 @@ module ActionDispatch
|
|
459
437
|
def write_cookie?(cookie)
|
460
438
|
request.ssl? || !cookie[:secure] || always_write_cookie
|
461
439
|
end
|
440
|
+
|
441
|
+
def handle_options(options)
|
442
|
+
if options[:expires].respond_to?(:from_now)
|
443
|
+
options[:expires] = options[:expires].from_now
|
444
|
+
end
|
445
|
+
|
446
|
+
options[:path] ||= "/"
|
447
|
+
|
448
|
+
cookies_same_site_protection = request.cookies_same_site_protection
|
449
|
+
options[:same_site] ||= cookies_same_site_protection.call(request)
|
450
|
+
|
451
|
+
if options[:domain] == :all || options[:domain] == "all"
|
452
|
+
# If there is a provided tld length then we use it otherwise default domain regexp.
|
453
|
+
domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP
|
454
|
+
|
455
|
+
# If host is not ip and matches domain regexp.
|
456
|
+
# (ip confirms to domain regexp so we explicitly check for ip)
|
457
|
+
options[:domain] = if !request.host.match?(/^[\d.]+$/) && (request.host =~ domain_regexp)
|
458
|
+
".#{$&}"
|
459
|
+
end
|
460
|
+
elsif options[:domain].is_a? Array
|
461
|
+
# If host matches one of the supplied domains.
|
462
|
+
options[:domain] = options[:domain].find do |domain|
|
463
|
+
domain = domain.delete_prefix(".")
|
464
|
+
request.host == domain || request.host.end_with?(".#{domain}")
|
465
|
+
end
|
466
|
+
end
|
467
|
+
end
|
462
468
|
end
|
463
469
|
|
464
470
|
class AbstractCookieJar # :nodoc:
|
@@ -470,7 +476,13 @@ module ActionDispatch
|
|
470
476
|
|
471
477
|
def [](name)
|
472
478
|
if data = @parent_jar[name.to_s]
|
473
|
-
parse
|
479
|
+
result = parse(name, data, purpose: "cookie.#{name}")
|
480
|
+
|
481
|
+
if result.nil?
|
482
|
+
parse(name, data)
|
483
|
+
else
|
484
|
+
result
|
485
|
+
end
|
474
486
|
end
|
475
487
|
end
|
476
488
|
|
@@ -481,7 +493,7 @@ module ActionDispatch
|
|
481
493
|
options = { value: options }
|
482
494
|
end
|
483
495
|
|
484
|
-
commit(options)
|
496
|
+
commit(name, options)
|
485
497
|
@parent_jar[name] = options
|
486
498
|
end
|
487
499
|
|
@@ -490,28 +502,42 @@ module ActionDispatch
|
|
490
502
|
|
491
503
|
private
|
492
504
|
def expiry_options(options)
|
493
|
-
if
|
494
|
-
|
495
|
-
{ expires_in: options[:expires] }
|
496
|
-
else
|
497
|
-
{ expires_at: options[:expires] }
|
498
|
-
end
|
505
|
+
if options[:expires].respond_to?(:from_now)
|
506
|
+
{ expires_in: options[:expires] }
|
499
507
|
else
|
500
|
-
{}
|
508
|
+
{ expires_at: options[:expires] }
|
501
509
|
end
|
502
510
|
end
|
503
511
|
|
504
|
-
def
|
505
|
-
|
512
|
+
def cookie_metadata(name, options)
|
513
|
+
expiry_options(options).tap do |metadata|
|
514
|
+
metadata[:purpose] = "cookie.#{name}" if request.use_cookies_with_metadata
|
515
|
+
end
|
516
|
+
end
|
517
|
+
|
518
|
+
def parse(name, data, purpose: nil); data; end
|
519
|
+
def commit(name, options); end
|
506
520
|
end
|
507
521
|
|
508
522
|
class PermanentCookieJar < AbstractCookieJar # :nodoc:
|
509
523
|
private
|
510
|
-
def commit(options)
|
524
|
+
def commit(name, options)
|
511
525
|
options[:expires] = 20.years.from_now
|
512
526
|
end
|
513
527
|
end
|
514
528
|
|
529
|
+
class MarshalWithJsonFallback # :nodoc:
|
530
|
+
def self.load(value)
|
531
|
+
Marshal.load(value)
|
532
|
+
rescue TypeError => e
|
533
|
+
ActiveSupport::JSON.decode(value) rescue raise e
|
534
|
+
end
|
535
|
+
|
536
|
+
def self.dump(value)
|
537
|
+
Marshal.dump(value)
|
538
|
+
end
|
539
|
+
end
|
540
|
+
|
515
541
|
class JsonSerializer # :nodoc:
|
516
542
|
def self.load(value)
|
517
543
|
ActiveSupport::JSON.decode(value)
|
@@ -523,7 +549,7 @@ module ActionDispatch
|
|
523
549
|
end
|
524
550
|
|
525
551
|
module SerializedCookieJars # :nodoc:
|
526
|
-
MARSHAL_SIGNATURE = "\x04\x08"
|
552
|
+
MARSHAL_SIGNATURE = "\x04\x08"
|
527
553
|
SERIALIZER = ActiveSupport::MessageEncryptor::NullSerializer
|
528
554
|
|
529
555
|
protected
|
@@ -542,9 +568,13 @@ module ActionDispatch
|
|
542
568
|
if value
|
543
569
|
case
|
544
570
|
when needs_migration?(value)
|
545
|
-
|
571
|
+
Marshal.load(value).tap do |v|
|
572
|
+
self[name] = { value: v }
|
573
|
+
end
|
546
574
|
when rotate
|
547
|
-
|
575
|
+
serializer.load(value).tap do |v|
|
576
|
+
self[name] = { value: v }
|
577
|
+
end
|
548
578
|
else
|
549
579
|
serializer.load(value)
|
550
580
|
end
|
@@ -555,7 +585,7 @@ module ActionDispatch
|
|
555
585
|
serializer = request.cookies_serializer || :marshal
|
556
586
|
case serializer
|
557
587
|
when :marshal
|
558
|
-
|
588
|
+
MarshalWithJsonFallback
|
559
589
|
when :json, :hybrid
|
560
590
|
JsonSerializer
|
561
591
|
else
|
@@ -577,24 +607,21 @@ module ActionDispatch
|
|
577
607
|
secret = request.key_generator.generate_key(request.signed_cookie_salt)
|
578
608
|
@verifier = ActiveSupport::MessageVerifier.new(secret, digest: signed_cookie_digest, serializer: SERIALIZER)
|
579
609
|
|
580
|
-
request.cookies_rotations.signed.each do
|
610
|
+
request.cookies_rotations.signed.each do |(*secrets)|
|
611
|
+
options = secrets.extract_options!
|
581
612
|
@verifier.rotate(*secrets, serializer: SERIALIZER, **options)
|
582
613
|
end
|
583
|
-
|
584
|
-
if upgrade_legacy_signed_cookies?
|
585
|
-
@verifier.rotate request.secret_token, serializer: SERIALIZER
|
586
|
-
end
|
587
614
|
end
|
588
615
|
|
589
616
|
private
|
590
|
-
def parse(name, signed_message)
|
617
|
+
def parse(name, signed_message, purpose: nil)
|
591
618
|
deserialize(name) do |rotate|
|
592
|
-
@verifier.verified(signed_message, on_rotation: rotate)
|
619
|
+
@verifier.verified(signed_message, on_rotation: rotate, purpose: purpose)
|
593
620
|
end
|
594
621
|
end
|
595
622
|
|
596
|
-
def commit(options)
|
597
|
-
options[:value] = @verifier.generate(serialize(options[:value]),
|
623
|
+
def commit(name, options)
|
624
|
+
options[:value] = @verifier.generate(serialize(options[:value]), **cookie_metadata(name, options))
|
598
625
|
|
599
626
|
raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
|
600
627
|
end
|
@@ -617,7 +644,8 @@ module ActionDispatch
|
|
617
644
|
@encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, cipher: "aes-256-cbc", serializer: SERIALIZER)
|
618
645
|
end
|
619
646
|
|
620
|
-
request.cookies_rotations.encrypted.each do
|
647
|
+
request.cookies_rotations.encrypted.each do |(*secrets)|
|
648
|
+
options = secrets.extract_options!
|
621
649
|
@encryptor.rotate(*secrets, serializer: SERIALIZER, **options)
|
622
650
|
end
|
623
651
|
|
@@ -627,37 +655,28 @@ module ActionDispatch
|
|
627
655
|
sign_secret = request.key_generator.generate_key(request.encrypted_signed_cookie_salt)
|
628
656
|
|
629
657
|
@encryptor.rotate(secret, sign_secret, cipher: legacy_cipher, digest: digest, serializer: SERIALIZER)
|
630
|
-
|
658
|
+
elsif prepare_upgrade_legacy_hmac_aes_cbc_cookies?
|
659
|
+
future_cipher = encrypted_cookie_cipher
|
660
|
+
secret = request.key_generator.generate_key(request.authenticated_encrypted_cookie_salt, ActiveSupport::MessageEncryptor.key_len(future_cipher))
|
631
661
|
|
632
|
-
|
633
|
-
@legacy_verifier = ActiveSupport::MessageVerifier.new(request.secret_token, digest: digest, serializer: SERIALIZER)
|
662
|
+
@encryptor.rotate(secret, nil, cipher: future_cipher, serializer: SERIALIZER)
|
634
663
|
end
|
635
664
|
end
|
636
665
|
|
637
666
|
private
|
638
|
-
def parse(name, encrypted_message)
|
667
|
+
def parse(name, encrypted_message, purpose: nil)
|
639
668
|
deserialize(name) do |rotate|
|
640
|
-
@encryptor.decrypt_and_verify(encrypted_message, on_rotation: rotate)
|
669
|
+
@encryptor.decrypt_and_verify(encrypted_message, on_rotation: rotate, purpose: purpose)
|
641
670
|
end
|
642
671
|
rescue ActiveSupport::MessageEncryptor::InvalidMessage, ActiveSupport::MessageVerifier::InvalidSignature
|
643
|
-
|
672
|
+
nil
|
644
673
|
end
|
645
674
|
|
646
|
-
def commit(options)
|
647
|
-
options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value]),
|
675
|
+
def commit(name, options)
|
676
|
+
options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value]), **cookie_metadata(name, options))
|
648
677
|
|
649
678
|
raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
|
650
679
|
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
680
|
end
|
662
681
|
|
663
682
|
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]}"
|