actionpack 5.2.7.1 → 6.0.0.beta1
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 +109 -472
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/lib/abstract_controller/base.rb +4 -2
- data/lib/abstract_controller/caching/fragments.rb +6 -21
- data/lib/abstract_controller/callbacks.rb +12 -0
- data/lib/abstract_controller/collector.rb +1 -1
- data/lib/abstract_controller/helpers.rb +2 -2
- data/lib/abstract_controller/railties/routes_helpers.rb +1 -1
- data/lib/action_controller/api.rb +2 -1
- data/lib/action_controller/base.rb +2 -7
- data/lib/action_controller/caching.rb +1 -1
- data/lib/action_controller/log_subscriber.rb +8 -5
- data/lib/action_controller/metal/conditional_get.rb +9 -3
- data/lib/action_controller/metal/data_streaming.rb +5 -6
- data/lib/action_controller/metal/default_headers.rb +17 -0
- data/lib/action_controller/metal/exceptions.rb +22 -1
- data/lib/action_controller/metal/flash.rb +5 -5
- data/lib/action_controller/metal/force_ssl.rb +17 -57
- data/lib/action_controller/metal/head.rb +1 -1
- data/lib/action_controller/metal/helpers.rb +1 -2
- data/lib/action_controller/metal/http_authentication.rb +21 -22
- data/lib/action_controller/metal/implicit_render.rb +2 -12
- data/lib/action_controller/metal/instrumentation.rb +3 -5
- data/lib/action_controller/metal/live.rb +28 -26
- data/lib/action_controller/metal/mime_responds.rb +13 -2
- data/lib/action_controller/metal/params_wrapper.rb +18 -14
- data/lib/action_controller/metal/redirecting.rb +32 -11
- data/lib/action_controller/metal/rendering.rb +1 -1
- data/lib/action_controller/metal/request_forgery_protection.rb +32 -97
- data/lib/action_controller/metal/strong_parameters.rb +57 -34
- data/lib/action_controller/metal/url_for.rb +1 -1
- data/lib/action_controller/metal.rb +2 -2
- data/lib/action_controller/railties/helpers.rb +1 -1
- data/lib/action_controller/renderer.rb +15 -2
- data/lib/action_controller/test_case.rb +5 -9
- data/lib/action_controller.rb +1 -0
- data/lib/action_dispatch/http/cache.rb +14 -10
- data/lib/action_dispatch/http/content_disposition.rb +45 -0
- data/lib/action_dispatch/http/content_security_policy.rb +17 -8
- data/lib/action_dispatch/http/filter_parameters.rb +8 -6
- data/lib/action_dispatch/http/filter_redirect.rb +1 -1
- data/lib/action_dispatch/http/headers.rb +1 -1
- data/lib/action_dispatch/http/mime_negotiation.rb +7 -10
- data/lib/action_dispatch/http/mime_type.rb +1 -5
- data/lib/action_dispatch/http/parameter_filter.rb +5 -79
- data/lib/action_dispatch/http/parameters.rb +13 -3
- data/lib/action_dispatch/http/request.rb +10 -13
- data/lib/action_dispatch/http/response.rb +14 -14
- data/lib/action_dispatch/http/upload.rb +5 -0
- data/lib/action_dispatch/http/url.rb +81 -81
- data/lib/action_dispatch/journey/formatter.rb +1 -1
- data/lib/action_dispatch/journey/nfa/simulator.rb +0 -2
- data/lib/action_dispatch/journey/nodes/node.rb +9 -8
- data/lib/action_dispatch/journey/path/pattern.rb +3 -4
- data/lib/action_dispatch/journey/router/utils.rb +10 -10
- data/lib/action_dispatch/journey/router.rb +0 -3
- data/lib/action_dispatch/journey/scanner.rb +11 -4
- data/lib/action_dispatch/journey/visitors.rb +1 -1
- data/lib/action_dispatch/middleware/callbacks.rb +2 -4
- data/lib/action_dispatch/middleware/cookies.rb +49 -70
- data/lib/action_dispatch/middleware/debug_exceptions.rb +32 -58
- data/lib/action_dispatch/middleware/debug_locks.rb +5 -5
- data/lib/action_dispatch/middleware/debug_view.rb +50 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +36 -7
- data/lib/action_dispatch/middleware/executor.rb +1 -1
- data/lib/action_dispatch/middleware/flash.rb +1 -1
- data/lib/action_dispatch/middleware/host_authorization.rb +103 -0
- data/lib/action_dispatch/middleware/remote_ip.rb +6 -8
- data/lib/action_dispatch/middleware/request_id.rb +2 -2
- data/lib/action_dispatch/middleware/session/abstract_store.rb +0 -14
- data/lib/action_dispatch/middleware/session/cache_store.rb +6 -11
- data/lib/action_dispatch/middleware/session/cookie_store.rb +11 -27
- data/lib/action_dispatch/middleware/ssl.rb +8 -8
- data/lib/action_dispatch/middleware/stack.rb +2 -2
- data/lib/action_dispatch/middleware/static.rb +5 -6
- 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 +20 -2
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +2 -2
- 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/routes/_table.html.erb +3 -0
- data/lib/action_dispatch/railtie.rb +1 -0
- data/lib/action_dispatch/request/session.rb +8 -6
- data/lib/action_dispatch/routing/inspector.rb +99 -50
- data/lib/action_dispatch/routing/mapper.rb +36 -29
- data/lib/action_dispatch/routing/polymorphic_routes.rb +7 -12
- data/lib/action_dispatch/routing/route_set.rb +11 -12
- data/lib/action_dispatch/routing/url_for.rb +1 -0
- data/lib/action_dispatch/routing.rb +3 -2
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +3 -3
- data/lib/action_dispatch/testing/assertions/response.rb +2 -3
- data/lib/action_dispatch/testing/assertions/routing.rb +7 -2
- data/lib/action_dispatch/testing/integration.rb +11 -5
- data/lib/action_dispatch/testing/test_process.rb +2 -2
- data/lib/action_dispatch/testing/test_response.rb +4 -32
- data/lib/action_dispatch.rb +7 -6
- data/lib/action_pack/gem_version.rb +4 -4
- data/lib/action_pack.rb +1 -1
- metadata +25 -23
@@ -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
|
@@ -81,6 +77,10 @@ module ActionDispatch
|
|
81
77
|
get_header Cookies::COOKIES_ROTATIONS
|
82
78
|
end
|
83
79
|
|
80
|
+
def use_cookies_with_metadata
|
81
|
+
get_header Cookies::USE_COOKIES_WITH_METADATA
|
82
|
+
end
|
83
|
+
|
84
84
|
# :startdoc:
|
85
85
|
end
|
86
86
|
|
@@ -168,20 +168,20 @@ module ActionDispatch
|
|
168
168
|
# * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
|
169
169
|
# only HTTP. Defaults to +false+.
|
170
170
|
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
|
-
|
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
|
+
SECRET_KEY_BASE = "action_dispatch.secret_key_base"
|
181
|
+
COOKIES_SERIALIZER = "action_dispatch.cookies_serializer"
|
182
|
+
COOKIES_DIGEST = "action_dispatch.cookies_digest"
|
183
|
+
COOKIES_ROTATIONS = "action_dispatch.cookies_rotations"
|
184
|
+
USE_COOKIES_WITH_METADATA = "action_dispatch.use_cookies_with_metadata"
|
185
185
|
|
186
186
|
# Cookies can typically store 4096 bytes.
|
187
187
|
MAX_COOKIE_SIZE = 4096
|
@@ -210,9 +210,6 @@ module ActionDispatch
|
|
210
210
|
# the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
|
211
211
|
# cookie was tampered with by the user (or a 3rd party), +nil+ will be returned.
|
212
212
|
#
|
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
213
|
# This jar requires that you set a suitable secret for the verification on your app's +secret_key_base+.
|
217
214
|
#
|
218
215
|
# Example:
|
@@ -228,9 +225,6 @@ module ActionDispatch
|
|
228
225
|
# Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
|
229
226
|
# If the cookie was tampered with by the user (or a 3rd party), +nil+ will be returned.
|
230
227
|
#
|
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
228
|
# If +config.action_dispatch.encrypted_cookie_salt+ and +config.action_dispatch.encrypted_signed_cookie_salt+
|
235
229
|
# are both set, legacy cookies encrypted with HMAC AES-256-CBC will be transparently upgraded.
|
236
230
|
#
|
@@ -259,10 +253,6 @@ module ActionDispatch
|
|
259
253
|
|
260
254
|
private
|
261
255
|
|
262
|
-
def upgrade_legacy_signed_cookies?
|
263
|
-
request.secret_token.present? && request.secret_key_base.present?
|
264
|
-
end
|
265
|
-
|
266
256
|
def upgrade_legacy_hmac_aes_cbc_cookies?
|
267
257
|
request.secret_key_base.present? &&
|
268
258
|
request.encrypted_signed_cookie_salt.present? &&
|
@@ -470,7 +460,7 @@ module ActionDispatch
|
|
470
460
|
|
471
461
|
def [](name)
|
472
462
|
if data = @parent_jar[name.to_s]
|
473
|
-
parse name, data
|
463
|
+
parse(name, data, purpose: "cookie.#{name}") || parse(name, data)
|
474
464
|
end
|
475
465
|
end
|
476
466
|
|
@@ -481,7 +471,7 @@ module ActionDispatch
|
|
481
471
|
options = { value: options }
|
482
472
|
end
|
483
473
|
|
484
|
-
commit(options)
|
474
|
+
commit(name, options)
|
485
475
|
@parent_jar[name] = options
|
486
476
|
end
|
487
477
|
|
@@ -490,24 +480,31 @@ module ActionDispatch
|
|
490
480
|
|
491
481
|
private
|
492
482
|
def expiry_options(options)
|
493
|
-
if
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
483
|
+
if options[:expires].respond_to?(:from_now)
|
484
|
+
{ expires_in: options[:expires] }
|
485
|
+
else
|
486
|
+
{ expires_at: options[:expires] }
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
def cookie_metadata(name, options)
|
491
|
+
if request.use_cookies_with_metadata
|
492
|
+
metadata = expiry_options(options)
|
493
|
+
metadata[:purpose] = "cookie.#{name}"
|
494
|
+
|
495
|
+
metadata
|
499
496
|
else
|
500
497
|
{}
|
501
498
|
end
|
502
499
|
end
|
503
500
|
|
504
|
-
def parse(name, data); data; end
|
505
|
-
def commit(options); end
|
501
|
+
def parse(name, data, purpose: nil); data; end
|
502
|
+
def commit(name, options); end
|
506
503
|
end
|
507
504
|
|
508
505
|
class PermanentCookieJar < AbstractCookieJar # :nodoc:
|
509
506
|
private
|
510
|
-
def commit(options)
|
507
|
+
def commit(name, options)
|
511
508
|
options[:expires] = 20.years.from_now
|
512
509
|
end
|
513
510
|
end
|
@@ -523,7 +520,7 @@ module ActionDispatch
|
|
523
520
|
end
|
524
521
|
|
525
522
|
module SerializedCookieJars # :nodoc:
|
526
|
-
MARSHAL_SIGNATURE = "\x04\x08"
|
523
|
+
MARSHAL_SIGNATURE = "\x04\x08"
|
527
524
|
SERIALIZER = ActiveSupport::MessageEncryptor::NullSerializer
|
528
525
|
|
529
526
|
protected
|
@@ -580,21 +577,17 @@ module ActionDispatch
|
|
580
577
|
request.cookies_rotations.signed.each do |*secrets, **options|
|
581
578
|
@verifier.rotate(*secrets, serializer: SERIALIZER, **options)
|
582
579
|
end
|
583
|
-
|
584
|
-
if upgrade_legacy_signed_cookies?
|
585
|
-
@verifier.rotate request.secret_token, serializer: SERIALIZER
|
586
|
-
end
|
587
580
|
end
|
588
581
|
|
589
582
|
private
|
590
|
-
def parse(name, signed_message)
|
583
|
+
def parse(name, signed_message, purpose: nil)
|
591
584
|
deserialize(name) do |rotate|
|
592
|
-
@verifier.verified(signed_message, on_rotation: rotate)
|
585
|
+
@verifier.verified(signed_message, on_rotation: rotate, purpose: purpose)
|
593
586
|
end
|
594
587
|
end
|
595
588
|
|
596
|
-
def commit(options)
|
597
|
-
options[:value] = @verifier.generate(serialize(options[:value]),
|
589
|
+
def commit(name, options)
|
590
|
+
options[:value] = @verifier.generate(serialize(options[:value]), cookie_metadata(name, options))
|
598
591
|
|
599
592
|
raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
|
600
593
|
end
|
@@ -628,36 +621,22 @@ module ActionDispatch
|
|
628
621
|
|
629
622
|
@encryptor.rotate(secret, sign_secret, cipher: legacy_cipher, digest: digest, serializer: SERIALIZER)
|
630
623
|
end
|
631
|
-
|
632
|
-
if upgrade_legacy_signed_cookies?
|
633
|
-
@legacy_verifier = ActiveSupport::MessageVerifier.new(request.secret_token, digest: digest, serializer: SERIALIZER)
|
634
|
-
end
|
635
624
|
end
|
636
625
|
|
637
626
|
private
|
638
|
-
def parse(name, encrypted_message)
|
627
|
+
def parse(name, encrypted_message, purpose: nil)
|
639
628
|
deserialize(name) do |rotate|
|
640
|
-
@encryptor.decrypt_and_verify(encrypted_message, on_rotation: rotate)
|
629
|
+
@encryptor.decrypt_and_verify(encrypted_message, on_rotation: rotate, purpose: purpose)
|
641
630
|
end
|
642
631
|
rescue ActiveSupport::MessageEncryptor::InvalidMessage, ActiveSupport::MessageVerifier::InvalidSignature
|
643
|
-
|
632
|
+
nil
|
644
633
|
end
|
645
634
|
|
646
|
-
def commit(options)
|
647
|
-
options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value]),
|
635
|
+
def commit(name, options)
|
636
|
+
options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value]), cookie_metadata(name, options))
|
648
637
|
|
649
638
|
raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
|
650
639
|
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
640
|
end
|
662
641
|
|
663
642
|
def initialize(app)
|
@@ -3,57 +3,26 @@
|
|
3
3
|
require "action_dispatch/http/request"
|
4
4
|
require "action_dispatch/middleware/exception_wrapper"
|
5
5
|
require "action_dispatch/routing/inspector"
|
6
|
+
|
6
7
|
require "action_view"
|
7
8
|
require "action_view/base"
|
8
9
|
|
9
|
-
require "pp"
|
10
|
-
|
11
10
|
module ActionDispatch
|
12
11
|
# This middleware is responsible for logging exceptions and
|
13
12
|
# showing a debugging page in case the request is local.
|
14
13
|
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
|
14
|
+
cattr_reader :interceptors, instance_accessor: false, default: []
|
44
15
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
super
|
49
|
-
end
|
50
|
-
end
|
16
|
+
def self.register_interceptor(object = nil, &block)
|
17
|
+
interceptor = object || block
|
18
|
+
interceptors << interceptor
|
51
19
|
end
|
52
20
|
|
53
|
-
def initialize(app, routes_app = nil, response_format = :default)
|
21
|
+
def initialize(app, routes_app = nil, response_format = :default, interceptors = self.class.interceptors)
|
54
22
|
@app = app
|
55
23
|
@routes_app = routes_app
|
56
24
|
@response_format = response_format
|
25
|
+
@interceptors = interceptors
|
57
26
|
end
|
58
27
|
|
59
28
|
def call(env)
|
@@ -67,12 +36,24 @@ module ActionDispatch
|
|
67
36
|
|
68
37
|
response
|
69
38
|
rescue Exception => exception
|
39
|
+
invoke_interceptors(request, exception)
|
70
40
|
raise exception unless request.show_exceptions?
|
71
41
|
render_exception(request, exception)
|
72
42
|
end
|
73
43
|
|
74
44
|
private
|
75
45
|
|
46
|
+
def invoke_interceptors(request, exception)
|
47
|
+
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
|
48
|
+
wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
|
49
|
+
|
50
|
+
@interceptors.each do |interceptor|
|
51
|
+
interceptor.call(request, exception)
|
52
|
+
rescue Exception
|
53
|
+
log_error(request, wrapper)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
76
57
|
def render_exception(request, exception)
|
77
58
|
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
|
78
59
|
wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
|
@@ -130,23 +111,13 @@ module ActionDispatch
|
|
130
111
|
end
|
131
112
|
|
132
113
|
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],
|
114
|
+
DebugView.new(
|
145
115
|
request: request,
|
116
|
+
exception_wrapper: wrapper,
|
146
117
|
exception: wrapper.exception,
|
147
|
-
traces: traces,
|
148
|
-
show_source_idx: source_to_show_id,
|
149
|
-
trace_to_show: trace_to_show,
|
118
|
+
traces: wrapper.traces,
|
119
|
+
show_source_idx: wrapper.source_to_show_id,
|
120
|
+
trace_to_show: wrapper.trace_to_show,
|
150
121
|
routes_inspector: routes_inspector(wrapper.exception),
|
151
122
|
source_extracts: wrapper.source_extracts,
|
152
123
|
line_number: wrapper.line_number,
|
@@ -168,11 +139,14 @@ module ActionDispatch
|
|
168
139
|
trace = wrapper.framework_trace if trace.empty?
|
169
140
|
|
170
141
|
ActiveSupport::Deprecation.silence do
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
142
|
+
message = []
|
143
|
+
message << " "
|
144
|
+
message << "#{exception.class} (#{exception.message}):"
|
145
|
+
message.concat(exception.annoted_source_code) if exception.respond_to?(:annoted_source_code)
|
146
|
+
message << " "
|
147
|
+
message.concat(trace)
|
148
|
+
|
149
|
+
log_array(logger, message)
|
176
150
|
end
|
177
151
|
end
|
178
152
|
|
@@ -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]}"
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pp"
|
4
|
+
|
5
|
+
require "action_view"
|
6
|
+
require "action_view/base"
|
7
|
+
|
8
|
+
module ActionDispatch
|
9
|
+
class DebugView < ActionView::Base # :nodoc:
|
10
|
+
RESCUES_TEMPLATE_PATH = File.expand_path("templates", __dir__)
|
11
|
+
|
12
|
+
def initialize(assigns)
|
13
|
+
super([RESCUES_TEMPLATE_PATH], assigns)
|
14
|
+
end
|
15
|
+
|
16
|
+
def debug_params(params)
|
17
|
+
clean_params = params.clone
|
18
|
+
clean_params.delete("action")
|
19
|
+
clean_params.delete("controller")
|
20
|
+
|
21
|
+
if clean_params.empty?
|
22
|
+
"None"
|
23
|
+
else
|
24
|
+
PP.pp(clean_params, +"", 200)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def debug_headers(headers)
|
29
|
+
if headers.present?
|
30
|
+
headers.inspect.gsub(",", ",\n")
|
31
|
+
else
|
32
|
+
"None"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def debug_hash(object)
|
37
|
+
object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
|
38
|
+
end
|
39
|
+
|
40
|
+
def render(*)
|
41
|
+
logger = ActionView::Base.logger
|
42
|
+
|
43
|
+
if logger && logger.respond_to?(:silence)
|
44
|
+
logger.silence { super }
|
45
|
+
else
|
46
|
+
super
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -12,6 +12,7 @@ module ActionDispatch
|
|
12
12
|
"ActionController::UnknownHttpMethod" => :method_not_allowed,
|
13
13
|
"ActionController::NotImplemented" => :not_implemented,
|
14
14
|
"ActionController::UnknownFormat" => :not_acceptable,
|
15
|
+
"ActionController::MissingExactTemplate" => :not_acceptable,
|
15
16
|
"ActionController::InvalidAuthenticityToken" => :unprocessable_entity,
|
16
17
|
"ActionController::InvalidCrossOriginRequest" => :unprocessable_entity,
|
17
18
|
"ActionDispatch::Http::Parameters::ParseError" => :bad_request,
|
@@ -22,18 +23,20 @@ module ActionDispatch
|
|
22
23
|
)
|
23
24
|
|
24
25
|
cattr_accessor :rescue_templates, default: Hash.new("diagnostics").merge!(
|
25
|
-
"ActionView::MissingTemplate"
|
26
|
-
"ActionController::RoutingError"
|
27
|
-
"AbstractController::ActionNotFound"
|
28
|
-
"ActiveRecord::StatementInvalid"
|
29
|
-
"ActionView::Template::Error"
|
26
|
+
"ActionView::MissingTemplate" => "missing_template",
|
27
|
+
"ActionController::RoutingError" => "routing_error",
|
28
|
+
"AbstractController::ActionNotFound" => "unknown_action",
|
29
|
+
"ActiveRecord::StatementInvalid" => "invalid_statement",
|
30
|
+
"ActionView::Template::Error" => "template_error",
|
31
|
+
"ActionController::MissingExactTemplate" => "missing_exact_template",
|
30
32
|
)
|
31
33
|
|
32
|
-
attr_reader :backtrace_cleaner, :exception, :line_number, :file
|
34
|
+
attr_reader :backtrace_cleaner, :exception, :wrapped_causes, :line_number, :file
|
33
35
|
|
34
36
|
def initialize(backtrace_cleaner, exception)
|
35
37
|
@backtrace_cleaner = backtrace_cleaner
|
36
38
|
@exception = original_exception(exception)
|
39
|
+
@wrapped_causes = wrapped_causes_for(exception, backtrace_cleaner)
|
37
40
|
|
38
41
|
expand_backtrace if exception.is_a?(SyntaxError) || exception.cause.is_a?(SyntaxError)
|
39
42
|
end
|
@@ -64,7 +67,11 @@ module ActionDispatch
|
|
64
67
|
full_trace_with_ids = []
|
65
68
|
|
66
69
|
full_trace.each_with_index do |trace, idx|
|
67
|
-
trace_with_id = {
|
70
|
+
trace_with_id = {
|
71
|
+
exception_object_id: @exception.object_id,
|
72
|
+
id: idx,
|
73
|
+
trace: trace
|
74
|
+
}
|
68
75
|
|
69
76
|
if application_trace.include?(trace)
|
70
77
|
application_trace_with_ids << trace_with_id
|
@@ -97,6 +104,18 @@ module ActionDispatch
|
|
97
104
|
end
|
98
105
|
end
|
99
106
|
|
107
|
+
def trace_to_show
|
108
|
+
if traces["Application Trace"].empty? && rescue_template != "routing_error"
|
109
|
+
"Full Trace"
|
110
|
+
else
|
111
|
+
"Application Trace"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def source_to_show_id
|
116
|
+
(traces[trace_to_show].first || {})[:id]
|
117
|
+
end
|
118
|
+
|
100
119
|
private
|
101
120
|
|
102
121
|
def backtrace
|
@@ -111,6 +130,16 @@ module ActionDispatch
|
|
111
130
|
end
|
112
131
|
end
|
113
132
|
|
133
|
+
def causes_for(exception)
|
134
|
+
return enum_for(__method__, exception) unless block_given?
|
135
|
+
|
136
|
+
yield exception while exception = exception.cause
|
137
|
+
end
|
138
|
+
|
139
|
+
def wrapped_causes_for(exception, backtrace_cleaner)
|
140
|
+
causes_for(exception).map { |cause| self.class.new(backtrace_cleaner, cause) }
|
141
|
+
end
|
142
|
+
|
114
143
|
def clean_backtrace(*args)
|
115
144
|
if backtrace_cleaner
|
116
145
|
backtrace_cleaner.clean(backtrace, *args)
|
@@ -38,7 +38,7 @@ module ActionDispatch
|
|
38
38
|
#
|
39
39
|
# See docs on the FlashHash class for more details about the flash.
|
40
40
|
class Flash
|
41
|
-
KEY = "action_dispatch.request.flash_hash"
|
41
|
+
KEY = "action_dispatch.request.flash_hash"
|
42
42
|
|
43
43
|
module RequestMethods
|
44
44
|
# Access the contents of the flash. Use <tt>flash["notice"]</tt> to
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action_dispatch/http/request"
|
4
|
+
|
5
|
+
module ActionDispatch
|
6
|
+
# This middleware guards from DNS rebinding attacks by white-listing the
|
7
|
+
# hosts a request can be sent to.
|
8
|
+
#
|
9
|
+
# When a request comes to an unauthorized host, the +response_app+
|
10
|
+
# application will be executed and rendered. If no +response_app+ is given, a
|
11
|
+
# default one will run, which responds with +403 Forbidden+.
|
12
|
+
class HostAuthorization
|
13
|
+
class Permissions # :nodoc:
|
14
|
+
def initialize(hosts)
|
15
|
+
@hosts = sanitize_hosts(hosts)
|
16
|
+
end
|
17
|
+
|
18
|
+
def empty?
|
19
|
+
@hosts.empty?
|
20
|
+
end
|
21
|
+
|
22
|
+
def allows?(host)
|
23
|
+
@hosts.any? do |allowed|
|
24
|
+
allowed === host
|
25
|
+
rescue
|
26
|
+
# IPAddr#=== raises an error if you give it a hostname instead of
|
27
|
+
# IP. Treat similar errors as blocked access.
|
28
|
+
false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def sanitize_hosts(hosts)
|
35
|
+
Array(hosts).map do |host|
|
36
|
+
case host
|
37
|
+
when Regexp then sanitize_regexp(host)
|
38
|
+
when String then sanitize_string(host)
|
39
|
+
else host
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def sanitize_regexp(host)
|
45
|
+
/\A#{host}\z/
|
46
|
+
end
|
47
|
+
|
48
|
+
def sanitize_string(host)
|
49
|
+
if host.start_with?(".")
|
50
|
+
/\A(.+\.)?#{Regexp.escape(host[1..-1])}\z/
|
51
|
+
else
|
52
|
+
host
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
DEFAULT_RESPONSE_APP = -> env do
|
58
|
+
request = Request.new(env)
|
59
|
+
|
60
|
+
format = request.xhr? ? "text/plain" : "text/html"
|
61
|
+
template = DebugView.new(host: request.host)
|
62
|
+
body = template.render(template: "rescues/blocked_host", layout: "rescues/layout")
|
63
|
+
|
64
|
+
[403, {
|
65
|
+
"Content-Type" => "#{format}; charset=#{Response.default_charset}",
|
66
|
+
"Content-Length" => body.bytesize.to_s,
|
67
|
+
}, [body]]
|
68
|
+
end
|
69
|
+
|
70
|
+
def initialize(app, hosts, response_app = nil)
|
71
|
+
@app = app
|
72
|
+
@permissions = Permissions.new(hosts)
|
73
|
+
@response_app = response_app || DEFAULT_RESPONSE_APP
|
74
|
+
end
|
75
|
+
|
76
|
+
def call(env)
|
77
|
+
return @app.call(env) if @permissions.empty?
|
78
|
+
|
79
|
+
request = Request.new(env)
|
80
|
+
|
81
|
+
if authorized?(request)
|
82
|
+
mark_as_authorized(request)
|
83
|
+
@app.call(env)
|
84
|
+
else
|
85
|
+
@response_app.call(env)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def authorized?(request)
|
92
|
+
origin_host = request.get_header("HTTP_HOST").to_s.sub(/:\d+\z/, "")
|
93
|
+
forwarded_host = request.x_forwarded_host.to_s.split(/,\s?/).last.to_s.sub(/:\d+\z/, "")
|
94
|
+
|
95
|
+
@permissions.allows?(origin_host) &&
|
96
|
+
(forwarded_host.blank? || @permissions.allows?(forwarded_host))
|
97
|
+
end
|
98
|
+
|
99
|
+
def mark_as_authorized(request)
|
100
|
+
request.set_header("action_dispatch.authorized_host", request.host)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|