actionpack 6.0.3.2 → 6.1.0
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 +258 -223
- data/MIT-LICENSE +1 -1
- data/lib/abstract_controller.rb +1 -0
- data/lib/abstract_controller/base.rb +35 -2
- data/lib/abstract_controller/callbacks.rb +2 -2
- data/lib/abstract_controller/helpers.rb +105 -90
- data/lib/abstract_controller/rendering.rb +9 -9
- data/lib/abstract_controller/translation.rb +8 -2
- data/lib/action_controller.rb +2 -3
- data/lib/action_controller/api.rb +2 -2
- data/lib/action_controller/base.rb +4 -2
- data/lib/action_controller/caching.rb +0 -1
- data/lib/action_controller/log_subscriber.rb +3 -3
- data/lib/action_controller/metal.rb +2 -2
- data/lib/action_controller/metal/conditional_get.rb +10 -2
- data/lib/action_controller/metal/content_security_policy.rb +1 -1
- data/lib/action_controller/metal/cookies.rb +3 -1
- data/lib/action_controller/metal/data_streaming.rb +1 -1
- data/lib/action_controller/metal/etag_with_template_digest.rb +2 -4
- data/lib/action_controller/metal/exceptions.rb +33 -0
- data/lib/action_controller/metal/head.rb +7 -4
- data/lib/action_controller/metal/helpers.rb +11 -1
- data/lib/action_controller/metal/http_authentication.rb +4 -2
- data/lib/action_controller/metal/implicit_render.rb +1 -1
- data/lib/action_controller/metal/instrumentation.rb +11 -9
- data/lib/action_controller/metal/live.rb +1 -1
- data/lib/action_controller/metal/logging.rb +20 -0
- data/lib/action_controller/metal/mime_responds.rb +6 -2
- data/lib/action_controller/metal/parameter_encoding.rb +35 -4
- data/lib/action_controller/metal/params_wrapper.rb +14 -8
- data/lib/action_controller/metal/permissions_policy.rb +46 -0
- data/lib/action_controller/metal/redirecting.rb +1 -1
- data/lib/action_controller/metal/rendering.rb +6 -0
- data/lib/action_controller/metal/request_forgery_protection.rb +48 -24
- data/lib/action_controller/metal/rescue.rb +1 -1
- data/lib/action_controller/metal/strong_parameters.rb +103 -15
- data/lib/action_controller/renderer.rb +24 -13
- data/lib/action_controller/test_case.rb +62 -56
- data/lib/action_dispatch.rb +3 -2
- data/lib/action_dispatch/http/cache.rb +12 -10
- data/lib/action_dispatch/http/content_disposition.rb +2 -2
- data/lib/action_dispatch/http/content_security_policy.rb +5 -1
- data/lib/action_dispatch/http/filter_parameters.rb +1 -1
- data/lib/action_dispatch/http/filter_redirect.rb +1 -1
- data/lib/action_dispatch/http/headers.rb +3 -2
- data/lib/action_dispatch/http/mime_negotiation.rb +20 -8
- data/lib/action_dispatch/http/mime_type.rb +28 -15
- data/lib/action_dispatch/http/parameters.rb +1 -19
- data/lib/action_dispatch/http/permissions_policy.rb +173 -0
- data/lib/action_dispatch/http/request.rb +26 -8
- data/lib/action_dispatch/http/response.rb +17 -16
- data/lib/action_dispatch/http/url.rb +3 -2
- data/lib/action_dispatch/journey.rb +0 -2
- data/lib/action_dispatch/journey/formatter.rb +53 -28
- data/lib/action_dispatch/journey/gtg/builder.rb +22 -36
- data/lib/action_dispatch/journey/gtg/simulator.rb +8 -7
- data/lib/action_dispatch/journey/gtg/transition_table.rb +6 -4
- data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
- data/lib/action_dispatch/journey/nodes/node.rb +4 -3
- 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 +13 -18
- data/lib/action_dispatch/journey/route.rb +7 -18
- data/lib/action_dispatch/journey/router.rb +26 -30
- data/lib/action_dispatch/journey/router/utils.rb +6 -4
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +9 -2
- data/lib/action_dispatch/middleware/cookies.rb +74 -33
- data/lib/action_dispatch/middleware/debug_exceptions.rb +10 -17
- data/lib/action_dispatch/middleware/debug_view.rb +1 -1
- data/lib/action_dispatch/middleware/exception_wrapper.rb +29 -17
- data/lib/action_dispatch/middleware/host_authorization.rb +25 -5
- data/lib/action_dispatch/middleware/public_exceptions.rb +1 -1
- data/lib/action_dispatch/middleware/remote_ip.rb +5 -4
- data/lib/action_dispatch/middleware/request_id.rb +4 -5
- data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -2
- data/lib/action_dispatch/middleware/session/cookie_store.rb +2 -2
- data/lib/action_dispatch/middleware/ssl.rb +9 -6
- data/lib/action_dispatch/middleware/stack.rb +18 -0
- data/lib/action_dispatch/middleware/static.rb +154 -93
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +2 -5
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +100 -8
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +12 -1
- data/lib/action_dispatch/railtie.rb +3 -2
- data/lib/action_dispatch/request/session.rb +2 -8
- data/lib/action_dispatch/request/utils.rb +26 -2
- data/lib/action_dispatch/routing/inspector.rb +8 -7
- data/lib/action_dispatch/routing/mapper.rb +102 -71
- data/lib/action_dispatch/routing/polymorphic_routes.rb +12 -11
- data/lib/action_dispatch/routing/redirection.rb +3 -3
- data/lib/action_dispatch/routing/route_set.rb +49 -41
- data/lib/action_dispatch/routing/url_for.rb +1 -0
- data/lib/action_dispatch/system_test_case.rb +29 -24
- data/lib/action_dispatch/system_testing/browser.rb +33 -27
- data/lib/action_dispatch/system_testing/driver.rb +6 -7
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +47 -6
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +4 -7
- data/lib/action_dispatch/testing/assertions.rb +1 -1
- data/lib/action_dispatch/testing/assertions/response.rb +2 -4
- data/lib/action_dispatch/testing/assertions/routing.rb +5 -5
- data/lib/action_dispatch/testing/integration.rb +38 -27
- data/lib/action_dispatch/testing/test_process.rb +29 -4
- data/lib/action_dispatch/testing/test_request.rb +3 -3
- data/lib/action_pack.rb +1 -1
- data/lib/action_pack/gem_version.rb +3 -3
- metadata +21 -22
- data/lib/action_controller/metal/force_ssl.rb +0 -58
- data/lib/action_dispatch/http/parameter_filter.rb +0 -12
- data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
- data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
- data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -119
@@ -19,11 +19,13 @@ module ActionDispatch
|
|
19
19
|
encoding = path.encoding
|
20
20
|
path = +"/#{path}"
|
21
21
|
path.squeeze!("/")
|
22
|
-
|
23
|
-
path
|
24
|
-
|
22
|
+
|
23
|
+
unless path == "/"
|
24
|
+
path.delete_suffix!("/")
|
25
|
+
path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase }
|
26
|
+
end
|
27
|
+
|
25
28
|
path.force_encoding(encoding)
|
26
|
-
path
|
27
29
|
end
|
28
30
|
|
29
31
|
# URI path and fragment escaping
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "erb"
|
4
|
+
require "uri"
|
4
5
|
require "action_dispatch/http/request"
|
5
6
|
require "active_support/actionable_error"
|
6
7
|
|
@@ -23,11 +24,17 @@ module ActionDispatch
|
|
23
24
|
|
24
25
|
private
|
25
26
|
def actionable_request?(request)
|
26
|
-
request.get_header("action_dispatch.show_detailed_exceptions")
|
27
|
+
request.get_header("action_dispatch.show_detailed_exceptions") && request.post? && request.path == endpoint
|
27
28
|
end
|
28
29
|
|
29
30
|
def redirect_to(location)
|
30
|
-
|
31
|
+
uri = URI.parse location
|
32
|
+
|
33
|
+
if uri.relative? || uri.scheme == "http" || uri.scheme == "https"
|
34
|
+
body = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(location)}\">redirected</a>.</body></html>"
|
35
|
+
else
|
36
|
+
return [400, { "Content-Type" => "text/plain" }, ["Invalid redirection URI"]]
|
37
|
+
end
|
31
38
|
|
32
39
|
[302, {
|
33
40
|
"Content-Type" => "text/html; charset=#{Response.default_charset}",
|
@@ -69,6 +69,10 @@ module ActionDispatch
|
|
69
69
|
get_header Cookies::COOKIES_SERIALIZER
|
70
70
|
end
|
71
71
|
|
72
|
+
def cookies_same_site_protection
|
73
|
+
get_header(Cookies::COOKIES_SAME_SITE_PROTECTION) || Proc.new { }
|
74
|
+
end
|
75
|
+
|
72
76
|
def cookies_digest
|
73
77
|
get_header Cookies::COOKIES_DIGEST
|
74
78
|
end
|
@@ -84,11 +88,10 @@ module ActionDispatch
|
|
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
|
@@ -181,6 +186,7 @@ module ActionDispatch
|
|
181
186
|
COOKIES_SERIALIZER = "action_dispatch.cookies_serializer"
|
182
187
|
COOKIES_DIGEST = "action_dispatch.cookies_digest"
|
183
188
|
COOKIES_ROTATIONS = "action_dispatch.cookies_rotations"
|
189
|
+
COOKIES_SAME_SITE_PROTECTION = "action_dispatch.cookies_same_site_protection"
|
184
190
|
USE_COOKIES_WITH_METADATA = "action_dispatch.use_cookies_with_metadata"
|
185
191
|
|
186
192
|
# Cookies can typically store 4096 bytes.
|
@@ -259,6 +265,12 @@ module ActionDispatch
|
|
259
265
|
request.use_authenticated_cookie_encryption
|
260
266
|
end
|
261
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
|
+
|
262
274
|
def encrypted_cookie_cipher
|
263
275
|
request.encrypted_cookie_cipher || "aes-256-gcm"
|
264
276
|
end
|
@@ -286,9 +298,9 @@ module ActionDispatch
|
|
286
298
|
DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/
|
287
299
|
|
288
300
|
def self.build(req, cookies)
|
289
|
-
new(req)
|
290
|
-
|
291
|
-
|
301
|
+
jar = new(req)
|
302
|
+
jar.update(cookies)
|
303
|
+
jar
|
292
304
|
end
|
293
305
|
|
294
306
|
attr_reader :request
|
@@ -346,28 +358,6 @@ module ActionDispatch
|
|
346
358
|
@cookies.map { |k, v| "#{escape(k)}=#{escape(v)}" }.join "; "
|
347
359
|
end
|
348
360
|
|
349
|
-
def handle_options(options) # :nodoc:
|
350
|
-
if options[:expires].respond_to?(:from_now)
|
351
|
-
options[:expires] = options[:expires].from_now
|
352
|
-
end
|
353
|
-
|
354
|
-
options[:path] ||= "/"
|
355
|
-
|
356
|
-
if options[:domain] == :all || options[:domain] == "all"
|
357
|
-
# If there is a provided tld length then we use it otherwise default domain regexp.
|
358
|
-
domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP
|
359
|
-
|
360
|
-
# If host is not ip and matches domain regexp.
|
361
|
-
# (ip confirms to domain regexp so we explicitly check for ip)
|
362
|
-
options[:domain] = if (request.host !~ /^[\d.]+$/) && (request.host =~ domain_regexp)
|
363
|
-
".#{$&}"
|
364
|
-
end
|
365
|
-
elsif options[:domain].is_a? Array
|
366
|
-
# If host matches one of the supplied domains without a dot in front of it.
|
367
|
-
options[:domain] = options[:domain].find { |domain| request.host.include? domain.sub(/^\./, "") }
|
368
|
-
end
|
369
|
-
end
|
370
|
-
|
371
361
|
# Sets the cookie named +name+. The second argument may be the cookie's
|
372
362
|
# value or a hash of options as documented above.
|
373
363
|
def []=(name, options)
|
@@ -447,6 +437,34 @@ module ActionDispatch
|
|
447
437
|
def write_cookie?(cookie)
|
448
438
|
request.ssl? || !cookie[:secure] || always_write_cookie
|
449
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
|
450
468
|
end
|
451
469
|
|
452
470
|
class AbstractCookieJar # :nodoc:
|
@@ -458,7 +476,13 @@ module ActionDispatch
|
|
458
476
|
|
459
477
|
def [](name)
|
460
478
|
if data = @parent_jar[name.to_s]
|
461
|
-
parse(name, data, purpose: "cookie.#{name}")
|
479
|
+
result = parse(name, data, purpose: "cookie.#{name}")
|
480
|
+
|
481
|
+
if result.nil?
|
482
|
+
parse(name, data)
|
483
|
+
else
|
484
|
+
result
|
485
|
+
end
|
462
486
|
end
|
463
487
|
end
|
464
488
|
|
@@ -502,6 +526,18 @@ module ActionDispatch
|
|
502
526
|
end
|
503
527
|
end
|
504
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
|
+
|
505
541
|
class JsonSerializer # :nodoc:
|
506
542
|
def self.load(value)
|
507
543
|
ActiveSupport::JSON.decode(value)
|
@@ -549,7 +585,7 @@ module ActionDispatch
|
|
549
585
|
serializer = request.cookies_serializer || :marshal
|
550
586
|
case serializer
|
551
587
|
when :marshal
|
552
|
-
|
588
|
+
MarshalWithJsonFallback
|
553
589
|
when :json, :hybrid
|
554
590
|
JsonSerializer
|
555
591
|
else
|
@@ -619,6 +655,11 @@ module ActionDispatch
|
|
619
655
|
sign_secret = request.key_generator.generate_key(request.encrypted_signed_cookie_salt)
|
620
656
|
|
621
657
|
@encryptor.rotate(secret, sign_secret, cipher: legacy_cipher, digest: digest, serializer: SERIALIZER)
|
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))
|
661
|
+
|
662
|
+
@encryptor.rotate(secret, nil, cipher: future_cipher, serializer: SERIALIZER)
|
622
663
|
end
|
623
664
|
end
|
624
665
|
|
@@ -4,10 +4,7 @@ require "action_dispatch/http/request"
|
|
4
4
|
require "action_dispatch/middleware/exception_wrapper"
|
5
5
|
require "action_dispatch/routing/inspector"
|
6
6
|
|
7
|
-
require "active_support/actionable_error"
|
8
|
-
|
9
7
|
require "action_view"
|
10
|
-
require "action_view/base"
|
11
8
|
|
12
9
|
module ActionDispatch
|
13
10
|
# This middleware is responsible for logging exceptions and
|
@@ -63,8 +60,8 @@ module ActionDispatch
|
|
63
60
|
if request.get_header("action_dispatch.show_detailed_exceptions")
|
64
61
|
begin
|
65
62
|
content_type = request.formats.first
|
66
|
-
rescue
|
67
|
-
|
63
|
+
rescue ActionDispatch::Http::MimeNegotiation::InvalidType
|
64
|
+
content_type = Mime[:text]
|
68
65
|
end
|
69
66
|
|
70
67
|
if api_request?(content_type)
|
@@ -140,20 +137,16 @@ module ActionDispatch
|
|
140
137
|
return unless logger
|
141
138
|
|
142
139
|
exception = wrapper.exception
|
140
|
+
trace = wrapper.exception_trace
|
143
141
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
message << "#{exception.class} (#{exception.message}):"
|
151
|
-
message.concat(exception.annotated_source_code) if exception.respond_to?(:annotated_source_code)
|
152
|
-
message << " "
|
153
|
-
message.concat(trace)
|
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)
|
154
148
|
|
155
|
-
|
156
|
-
end
|
149
|
+
log_array(logger, message)
|
157
150
|
end
|
158
151
|
|
159
152
|
def log_array(logger, array)
|
@@ -6,21 +6,21 @@ require "rack/utils"
|
|
6
6
|
module ActionDispatch
|
7
7
|
class ExceptionWrapper
|
8
8
|
cattr_accessor :rescue_responses, default: Hash.new(:internal_server_error).merge!(
|
9
|
-
"ActionController::RoutingError"
|
10
|
-
"AbstractController::ActionNotFound"
|
11
|
-
"ActionController::MethodNotAllowed"
|
12
|
-
"ActionController::UnknownHttpMethod"
|
13
|
-
"ActionController::NotImplemented"
|
14
|
-
"ActionController::UnknownFormat"
|
15
|
-
"
|
16
|
-
"ActionController::MissingExactTemplate"
|
17
|
-
"ActionController::InvalidAuthenticityToken"
|
18
|
-
"ActionController::InvalidCrossOriginRequest"
|
19
|
-
"ActionDispatch::Http::Parameters::ParseError"
|
20
|
-
"ActionController::BadRequest"
|
21
|
-
"ActionController::ParameterMissing"
|
22
|
-
"Rack::QueryParser::ParameterTypeError"
|
23
|
-
"Rack::QueryParser::InvalidParameterError"
|
9
|
+
"ActionController::RoutingError" => :not_found,
|
10
|
+
"AbstractController::ActionNotFound" => :not_found,
|
11
|
+
"ActionController::MethodNotAllowed" => :method_not_allowed,
|
12
|
+
"ActionController::UnknownHttpMethod" => :method_not_allowed,
|
13
|
+
"ActionController::NotImplemented" => :not_implemented,
|
14
|
+
"ActionController::UnknownFormat" => :not_acceptable,
|
15
|
+
"ActionDispatch::Http::MimeNegotiation::InvalidType" => :not_acceptable,
|
16
|
+
"ActionController::MissingExactTemplate" => :not_acceptable,
|
17
|
+
"ActionController::InvalidAuthenticityToken" => :unprocessable_entity,
|
18
|
+
"ActionController::InvalidCrossOriginRequest" => :unprocessable_entity,
|
19
|
+
"ActionDispatch::Http::Parameters::ParseError" => :bad_request,
|
20
|
+
"ActionController::BadRequest" => :bad_request,
|
21
|
+
"ActionController::ParameterMissing" => :bad_request,
|
22
|
+
"Rack::QueryParser::ParameterTypeError" => :bad_request,
|
23
|
+
"Rack::QueryParser::InvalidParameterError" => :bad_request
|
24
24
|
)
|
25
25
|
|
26
26
|
cattr_accessor :rescue_templates, default: Hash.new("diagnostics").merge!(
|
@@ -36,18 +36,24 @@ module ActionDispatch
|
|
36
36
|
"ActionView::Template::Error"
|
37
37
|
]
|
38
38
|
|
39
|
+
cattr_accessor :silent_exceptions, default: [
|
40
|
+
"ActionController::RoutingError",
|
41
|
+
"ActionDispatch::Http::MimeNegotiation::InvalidType"
|
42
|
+
]
|
43
|
+
|
39
44
|
attr_reader :backtrace_cleaner, :exception, :wrapped_causes, :line_number, :file
|
40
45
|
|
41
46
|
def initialize(backtrace_cleaner, exception)
|
42
47
|
@backtrace_cleaner = backtrace_cleaner
|
43
48
|
@exception = exception
|
49
|
+
@exception_class_name = @exception.class.name
|
44
50
|
@wrapped_causes = wrapped_causes_for(exception, backtrace_cleaner)
|
45
51
|
|
46
52
|
expand_backtrace if exception.is_a?(SyntaxError) || exception.cause.is_a?(SyntaxError)
|
47
53
|
end
|
48
54
|
|
49
55
|
def unwrapped_exception
|
50
|
-
if wrapper_exceptions.include?(
|
56
|
+
if wrapper_exceptions.include?(@exception_class_name)
|
51
57
|
exception.cause
|
52
58
|
else
|
53
59
|
exception
|
@@ -55,13 +61,19 @@ module ActionDispatch
|
|
55
61
|
end
|
56
62
|
|
57
63
|
def rescue_template
|
58
|
-
@@rescue_templates[@
|
64
|
+
@@rescue_templates[@exception_class_name]
|
59
65
|
end
|
60
66
|
|
61
67
|
def status_code
|
62
68
|
self.class.status_code_for_exception(unwrapped_exception.class.name)
|
63
69
|
end
|
64
70
|
|
71
|
+
def exception_trace
|
72
|
+
trace = application_trace
|
73
|
+
trace = framework_trace if trace.empty? && !silent_exceptions.include?(@exception_class_name)
|
74
|
+
trace
|
75
|
+
end
|
76
|
+
|
65
77
|
def application_trace
|
66
78
|
clean_backtrace(:silent)
|
67
79
|
end
|
@@ -4,7 +4,12 @@ require "action_dispatch/http/request"
|
|
4
4
|
|
5
5
|
module ActionDispatch
|
6
6
|
# This middleware guards from DNS rebinding attacks by explicitly permitting
|
7
|
-
# the hosts a request can be sent to
|
7
|
+
# the hosts a request can be sent to, and is passed the options set in
|
8
|
+
# +config.host_authorization+.
|
9
|
+
#
|
10
|
+
# Requests can opt-out of Host Authorization with +exclude+:
|
11
|
+
#
|
12
|
+
# config.host_authorization = { exclude: ->(request) { request.path =~ /healthcheck/ } }
|
8
13
|
#
|
9
14
|
# When a request comes to an unauthorized host, the +response_app+
|
10
15
|
# application will be executed and rendered. If no +response_app+ is given, a
|
@@ -46,9 +51,9 @@ module ActionDispatch
|
|
46
51
|
|
47
52
|
def sanitize_string(host)
|
48
53
|
if host.start_with?(".")
|
49
|
-
/\A(.+\.)?#{Regexp.escape(host[1..-1])}\z/
|
54
|
+
/\A(.+\.)?#{Regexp.escape(host[1..-1])}\z/i
|
50
55
|
else
|
51
|
-
host
|
56
|
+
/\A#{host}\z/i
|
52
57
|
end
|
53
58
|
end
|
54
59
|
end
|
@@ -66,9 +71,20 @@ module ActionDispatch
|
|
66
71
|
}, [body]]
|
67
72
|
end
|
68
73
|
|
69
|
-
def initialize(app, hosts,
|
74
|
+
def initialize(app, hosts, deprecated_response_app = nil, exclude: nil, response_app: nil)
|
70
75
|
@app = app
|
71
76
|
@permissions = Permissions.new(hosts)
|
77
|
+
@exclude = exclude
|
78
|
+
|
79
|
+
unless deprecated_response_app.nil?
|
80
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
81
|
+
`action_dispatch.hosts_response_app` is deprecated and will be ignored in Rails 6.2.
|
82
|
+
Use the Host Authorization `response_app` setting instead.
|
83
|
+
MSG
|
84
|
+
|
85
|
+
response_app ||= deprecated_response_app
|
86
|
+
end
|
87
|
+
|
72
88
|
@response_app = response_app || DEFAULT_RESPONSE_APP
|
73
89
|
end
|
74
90
|
|
@@ -77,7 +93,7 @@ module ActionDispatch
|
|
77
93
|
|
78
94
|
request = Request.new(env)
|
79
95
|
|
80
|
-
if authorized?(request)
|
96
|
+
if authorized?(request) || excluded?(request)
|
81
97
|
mark_as_authorized(request)
|
82
98
|
@app.call(env)
|
83
99
|
else
|
@@ -94,6 +110,10 @@ module ActionDispatch
|
|
94
110
|
(forwarded_host.blank? || @permissions.allows?(forwarded_host))
|
95
111
|
end
|
96
112
|
|
113
|
+
def excluded?(request)
|
114
|
+
@exclude && @exclude.call(request)
|
115
|
+
end
|
116
|
+
|
97
117
|
def mark_as_authorized(request)
|
98
118
|
request.set_header("action_dispatch.authorized_host", request.host)
|
99
119
|
end
|
@@ -23,7 +23,7 @@ module ActionDispatch
|
|
23
23
|
status = request.path_info[1..-1].to_i
|
24
24
|
begin
|
25
25
|
content_type = request.formats.first
|
26
|
-
rescue
|
26
|
+
rescue ActionDispatch::Http::MimeNegotiation::InvalidType
|
27
27
|
content_type = Mime[:text]
|
28
28
|
end
|
29
29
|
body = { status: status, error: Rack::Utils::HTTP_STATUS_CODES.fetch(status, Rack::Utils::HTTP_STATUS_CODES[500]) }
|