actionpack 6.0.4.1 → 6.1.0.rc1
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 +241 -304
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- 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/abstract_controller.rb +1 -0
- 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/conditional_get.rb +10 -2
- data/lib/action_controller/metal/content_security_policy.rb +1 -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/feature_policy.rb +46 -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 +5 -3
- 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 +16 -11
- 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 +1 -1
- data/lib/action_controller/metal/rescue.rb +1 -1
- data/lib/action_controller/metal/strong_parameters.rb +103 -15
- data/lib/action_controller/metal.rb +2 -2
- data/lib/action_controller/renderer.rb +24 -13
- data/lib/action_controller/test_case.rb +62 -56
- data/lib/action_controller.rb +2 -3
- data/lib/action_dispatch/http/cache.rb +12 -10
- data/lib/action_dispatch/http/content_security_policy.rb +5 -1
- data/lib/action_dispatch/http/feature_policy.rb +168 -0
- 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 +14 -8
- data/lib/action_dispatch/http/mime_type.rb +29 -16
- data/lib/action_dispatch/http/parameters.rb +1 -19
- data/lib/action_dispatch/http/request.rb +24 -8
- data/lib/action_dispatch/http/response.rb +17 -16
- data/lib/action_dispatch/http/url.rb +3 -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/utils.rb +6 -4
- data/lib/action_dispatch/journey/router.rb +26 -30
- data/lib/action_dispatch/journey.rb +0 -2
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +1 -1
- data/lib/action_dispatch/middleware/cookies.rb +67 -32
- data/lib/action_dispatch/middleware/debug_exceptions.rb +8 -15
- data/lib/action_dispatch/middleware/debug_view.rb +1 -1
- data/lib/action_dispatch/middleware/exception_wrapper.rb +28 -16
- data/lib/action_dispatch/middleware/host_authorization.rb +29 -12
- 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 +18 -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 -3
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +88 -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 +16 -19
- data/lib/action_dispatch/routing/redirection.rb +3 -3
- data/lib/action_dispatch/routing/route_set.rb +49 -41
- 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/response.rb +2 -4
- data/lib/action_dispatch/testing/assertions/routing.rb +5 -5
- data/lib/action_dispatch/testing/assertions.rb +1 -1
- 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_dispatch.rb +3 -2
- data/lib/action_pack/gem_version.rb +3 -3
- data/lib/action_pack.rb +1 -1
- metadata +23 -24
- 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
@@ -40,11 +40,12 @@ module ActionDispatch
|
|
40
40
|
req.path_info = "/" + req.path_info unless req.path_info.start_with? "/"
|
41
41
|
end
|
42
42
|
|
43
|
-
|
44
|
-
|
43
|
+
tmp_params = set_params.merge route.defaults
|
44
|
+
parameters.each_pair { |key, val|
|
45
|
+
tmp_params[key] = val.force_encoding(::Encoding::UTF_8)
|
45
46
|
}
|
46
47
|
|
47
|
-
req.path_parameters =
|
48
|
+
req.path_parameters = tmp_params
|
48
49
|
|
49
50
|
status, headers, body = route.app.serve(req)
|
50
51
|
|
@@ -65,7 +66,8 @@ module ActionDispatch
|
|
65
66
|
find_routes(rails_req).each do |match, parameters, route|
|
66
67
|
unless route.path.anchored
|
67
68
|
rails_req.script_name = match.to_s
|
68
|
-
rails_req.path_info = match.post_match
|
69
|
+
rails_req.path_info = match.post_match
|
70
|
+
rails_req.path_info = "/" + rails_req.path_info unless rails_req.path_info.start_with? "/"
|
69
71
|
end
|
70
72
|
|
71
73
|
parameters = route.defaults.merge parameters
|
@@ -105,23 +107,24 @@ module ActionDispatch
|
|
105
107
|
end
|
106
108
|
|
107
109
|
def find_routes(req)
|
108
|
-
|
109
|
-
|
110
|
+
path_info = req.path_info
|
111
|
+
routes = filter_routes(path_info).concat custom_routes.find_all { |r|
|
112
|
+
r.path.match?(path_info)
|
110
113
|
}
|
111
114
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
end
|
115
|
+
if req.head?
|
116
|
+
routes = match_head_routes(routes, req)
|
117
|
+
else
|
118
|
+
routes.select! { |r| r.matches?(req) }
|
119
|
+
end
|
118
120
|
|
119
121
|
routes.sort_by!(&:precedence)
|
120
122
|
|
121
123
|
routes.map! { |r|
|
122
|
-
match_data = r.path.match(
|
124
|
+
match_data = r.path.match(path_info)
|
123
125
|
path_parameters = {}
|
124
|
-
match_data.names.
|
126
|
+
match_data.names.each_with_index { |name, i|
|
127
|
+
val = match_data[i + 1]
|
125
128
|
path_parameters[name.to_sym] = Utils.unescape_uri(val) if val
|
126
129
|
}
|
127
130
|
[match_data, path_parameters, r]
|
@@ -129,24 +132,17 @@ module ActionDispatch
|
|
129
132
|
end
|
130
133
|
|
131
134
|
def match_head_routes(routes, req)
|
132
|
-
|
133
|
-
head_routes
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
end
|
142
|
-
else
|
143
|
-
head_routes
|
135
|
+
head_routes = routes.select { |r| r.requires_matching_verb? && r.matches?(req) }
|
136
|
+
return head_routes unless head_routes.empty?
|
137
|
+
|
138
|
+
begin
|
139
|
+
req.request_method = "GET"
|
140
|
+
routes.select! { |r| r.matches?(req) }
|
141
|
+
routes
|
142
|
+
ensure
|
143
|
+
req.request_method = "HEAD"
|
144
144
|
end
|
145
145
|
end
|
146
|
-
|
147
|
-
def match_routes(routes, req)
|
148
|
-
routes.select { |r| r.matches?(req) }
|
149
|
-
end
|
150
146
|
end
|
151
147
|
end
|
152
148
|
end
|
@@ -24,7 +24,7 @@ module ActionDispatch
|
|
24
24
|
|
25
25
|
private
|
26
26
|
def actionable_request?(request)
|
27
|
-
request.get_header("action_dispatch.show_detailed_exceptions")
|
27
|
+
request.get_header("action_dispatch.show_detailed_exceptions") && request.post? && request.path == endpoint
|
28
28
|
end
|
29
29
|
|
30
30
|
def redirect_to(location)
|
@@ -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:
|
@@ -508,6 +526,18 @@ module ActionDispatch
|
|
508
526
|
end
|
509
527
|
end
|
510
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
|
+
|
511
541
|
class JsonSerializer # :nodoc:
|
512
542
|
def self.load(value)
|
513
543
|
ActiveSupport::JSON.decode(value)
|
@@ -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
|
@@ -625,6 +655,11 @@ module ActionDispatch
|
|
625
655
|
sign_secret = request.key_generator.generate_key(request.encrypted_signed_cookie_salt)
|
626
656
|
|
627
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)
|
628
663
|
end
|
629
664
|
end
|
630
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
|
@@ -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"
|
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
15
|
"ActionDispatch::Http::MimeNegotiation::InvalidType" => :not_acceptable,
|
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"
|
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/
|
50
55
|
else
|
51
|
-
|
56
|
+
host
|
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
|
@@ -86,15 +102,16 @@ module ActionDispatch
|
|
86
102
|
end
|
87
103
|
|
88
104
|
private
|
89
|
-
HOSTNAME = /[a-z0-9.-]+|\[[a-f0-9]*:[a-f0-9.:]+\]/i
|
90
|
-
VALID_ORIGIN_HOST = /\A(#{HOSTNAME})(?::\d+)?\z/
|
91
|
-
VALID_FORWARDED_HOST = /(?:\A|,[ ]?)(#{HOSTNAME})(?::\d+)?\z/
|
92
|
-
|
93
105
|
def authorized?(request)
|
94
|
-
origin_host = request.get_header("HTTP_HOST")
|
95
|
-
forwarded_host = request.x_forwarded_host
|
106
|
+
origin_host = request.get_header("HTTP_HOST").to_s.sub(/:\d+\z/, "")
|
107
|
+
forwarded_host = request.x_forwarded_host.to_s.split(/,\s?/).last.to_s.sub(/:\d+\z/, "")
|
108
|
+
|
109
|
+
@permissions.allows?(origin_host) &&
|
110
|
+
(forwarded_host.blank? || @permissions.allows?(forwarded_host))
|
111
|
+
end
|
96
112
|
|
97
|
-
|
113
|
+
def excluded?(request)
|
114
|
+
@exclude && @exclude.call(request)
|
98
115
|
end
|
99
116
|
|
100
117
|
def mark_as_authorized(request)
|