actionpack 5.2.4 → 6.0.2.1
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 +200 -317
- data/MIT-LICENSE +1 -1
- data/README.rdoc +3 -2
- data/lib/abstract_controller/base.rb +4 -2
- data/lib/abstract_controller/caching/fragments.rb +6 -22
- 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/abstract_controller/translation.rb +1 -0
- data/lib/action_controller.rb +5 -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.rb +1 -1
- data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
- 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/etag_with_template_digest.rb +1 -1
- data/lib/action_controller/metal/exceptions.rb +23 -2
- data/lib/action_controller/metal/flash.rb +5 -5
- data/lib/action_controller/metal/force_ssl.rb +15 -56
- data/lib/action_controller/metal/head.rb +1 -1
- data/lib/action_controller/metal/helpers.rb +3 -4
- data/lib/action_controller/metal/http_authentication.rb +20 -21
- data/lib/action_controller/metal/implicit_render.rb +4 -14
- data/lib/action_controller/metal/instrumentation.rb +3 -5
- data/lib/action_controller/metal/live.rb +29 -27
- data/lib/action_controller/metal/mime_responds.rb +13 -2
- data/lib/action_controller/metal/params_wrapper.rb +17 -13
- data/lib/action_controller/metal/redirecting.rb +5 -5
- data/lib/action_controller/metal/renderers.rb +4 -4
- data/lib/action_controller/metal/rendering.rb +2 -2
- data/lib/action_controller/metal/request_forgery_protection.rb +23 -12
- data/lib/action_controller/metal/strong_parameters.rb +63 -44
- data/lib/action_controller/metal/url_for.rb +1 -1
- data/lib/action_controller/railties/helpers.rb +1 -1
- data/lib/action_controller/renderer.rb +16 -3
- data/lib/action_controller/template_assertions.rb +1 -1
- data/lib/action_controller/test_case.rb +2 -5
- data/lib/action_dispatch.rb +9 -6
- 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 +28 -16
- 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 -5
- data/lib/action_dispatch/http/mime_type.rb +14 -6
- 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 +40 -20
- data/lib/action_dispatch/http/upload.rb +9 -1
- data/lib/action_dispatch/http/url.rb +81 -81
- data/lib/action_dispatch/journey/formatter.rb +2 -2
- 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 +6 -2
- data/lib/action_dispatch/journey/route.rb +5 -4
- data/lib/action_dispatch/journey/router.rb +0 -3
- data/lib/action_dispatch/journey/router/utils.rb +10 -10
- data/lib/action_dispatch/journey/routes.rb +0 -1
- data/lib/action_dispatch/journey/scanner.rb +11 -4
- data/lib/action_dispatch/journey/visitors.rb +1 -1
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +39 -0
- data/lib/action_dispatch/middleware/callbacks.rb +2 -4
- data/lib/action_dispatch/middleware/cookies.rb +52 -74
- data/lib/action_dispatch/middleware/debug_exceptions.rb +39 -59
- data/lib/action_dispatch/middleware/debug_locks.rb +5 -5
- data/lib/action_dispatch/middleware/debug_view.rb +68 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +49 -15
- data/lib/action_dispatch/middleware/flash.rb +1 -1
- data/lib/action_dispatch/middleware/host_authorization.rb +103 -0
- data/lib/action_dispatch/middleware/public_exceptions.rb +6 -2
- data/lib/action_dispatch/middleware/remote_ip.rb +9 -11
- data/lib/action_dispatch/middleware/request_id.rb +2 -2
- data/lib/action_dispatch/middleware/session/abstract_store.rb +14 -0
- data/lib/action_dispatch/middleware/session/cache_store.rb +11 -6
- data/lib/action_dispatch/middleware/session/cookie_store.rb +18 -13
- data/lib/action_dispatch/middleware/show_exceptions.rb +1 -1
- data/lib/action_dispatch/middleware/ssl.rb +8 -8
- data/lib/action_dispatch/middleware/stack.rb +33 -1
- data/lib/action_dispatch/middleware/static.rb +5 -6
- 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/_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 +26 -4
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +7 -4
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +4 -2
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +4 -0
- 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 +7 -2
- data/lib/action_dispatch/request/session.rb +15 -1
- data/lib/action_dispatch/routing.rb +21 -20
- data/lib/action_dispatch/routing/inspector.rb +99 -50
- data/lib/action_dispatch/routing/mapper.rb +61 -39
- data/lib/action_dispatch/routing/polymorphic_routes.rb +3 -4
- data/lib/action_dispatch/routing/route_set.rb +24 -27
- data/lib/action_dispatch/routing/url_for.rb +1 -0
- data/lib/action_dispatch/system_test_case.rb +44 -5
- data/lib/action_dispatch/system_testing/browser.rb +38 -7
- data/lib/action_dispatch/system_testing/driver.rb +10 -1
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +6 -5
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +7 -6
- data/lib/action_dispatch/testing/assertions.rb +1 -1
- data/lib/action_dispatch/testing/assertions/response.rb +2 -3
- data/lib/action_dispatch/testing/assertions/routing.rb +15 -3
- data/lib/action_dispatch/testing/integration.rb +12 -5
- data/lib/action_dispatch/testing/request_encoder.rb +2 -2
- data/lib/action_dispatch/testing/test_process.rb +2 -2
- data/lib/action_dispatch/testing/test_response.rb +4 -32
- data/lib/action_pack.rb +1 -1
- data/lib/action_pack/gem_version.rb +4 -4
- metadata +34 -15
- data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +0 -26
@@ -21,8 +21,12 @@ module ActionDispatch
|
|
21
21
|
def call(env)
|
22
22
|
request = ActionDispatch::Request.new(env)
|
23
23
|
status = request.path_info[1..-1].to_i
|
24
|
-
|
25
|
-
|
24
|
+
begin
|
25
|
+
content_type = request.formats.first
|
26
|
+
rescue Mime::Type::InvalidMimeType
|
27
|
+
content_type = Mime[:text]
|
28
|
+
end
|
29
|
+
body = { status: status, error: Rack::Utils::HTTP_STATUS_CODES.fetch(status, Rack::Utils::HTTP_STATUS_CODES[500]) }
|
26
30
|
|
27
31
|
render(status, content_type, body)
|
28
32
|
end
|
@@ -8,13 +8,13 @@ module ActionDispatch
|
|
8
8
|
# contain the address, and then picking the last-set address that is not
|
9
9
|
# on the list of trusted IPs. This follows the precedent set by e.g.
|
10
10
|
# {the Tomcat server}[https://issues.apache.org/bugzilla/show_bug.cgi?id=50453],
|
11
|
-
# with {reasoning explained at length}[
|
11
|
+
# with {reasoning explained at length}[https://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection]
|
12
12
|
# by @gingerlime. A more detailed explanation of the algorithm is given
|
13
13
|
# at GetIp#calculate_ip.
|
14
14
|
#
|
15
15
|
# Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2]
|
16
16
|
# requires. Some Rack servers simply drop preceding headers, and only report
|
17
|
-
# the value that was {given in the last header}[
|
17
|
+
# the value that was {given in the last header}[https://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-servers].
|
18
18
|
# If you are behind multiple proxy servers (like NGINX to HAProxy to Unicorn)
|
19
19
|
# then you should test your Rack server to make sure your data is good.
|
20
20
|
#
|
@@ -102,7 +102,7 @@ module ActionDispatch
|
|
102
102
|
# proxies, that header may contain a list of IPs. Other proxy services
|
103
103
|
# set the Client-Ip header instead, so we check that too.
|
104
104
|
#
|
105
|
-
# As discussed in {this post about Rails IP Spoofing}[
|
105
|
+
# As discussed in {this post about Rails IP Spoofing}[https://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/],
|
106
106
|
# while the first IP in the list is likely to be the "originating" IP,
|
107
107
|
# it could also have been set by the client maliciously.
|
108
108
|
#
|
@@ -162,14 +162,12 @@ module ActionDispatch
|
|
162
162
|
# Split the comma-separated list into an array of strings.
|
163
163
|
ips = header.strip.split(/[,\s]+/)
|
164
164
|
ips.select do |ip|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
nil
|
172
|
-
end
|
165
|
+
# Only return IPs that are valid according to the IPAddr#new method.
|
166
|
+
range = IPAddr.new(ip).to_range
|
167
|
+
# We want to make sure nobody is sneaking a netmask in.
|
168
|
+
range.begin == range.end
|
169
|
+
rescue ArgumentError
|
170
|
+
nil
|
173
171
|
end
|
174
172
|
end
|
175
173
|
|
@@ -15,7 +15,7 @@ module ActionDispatch
|
|
15
15
|
# The unique request id can be used to trace a request end-to-end and would typically end up being part of log files
|
16
16
|
# from multiple pieces of the stack.
|
17
17
|
class RequestId
|
18
|
-
X_REQUEST_ID = "X-Request-Id"
|
18
|
+
X_REQUEST_ID = "X-Request-Id" #:nodoc:
|
19
19
|
|
20
20
|
def initialize(app)
|
21
21
|
@app = app
|
@@ -30,7 +30,7 @@ module ActionDispatch
|
|
30
30
|
private
|
31
31
|
def make_request_id(request_id)
|
32
32
|
if request_id.presence
|
33
|
-
request_id.gsub(/[^\w\-@]/, ""
|
33
|
+
request_id.gsub(/[^\w\-@]/, "").first(255)
|
34
34
|
else
|
35
35
|
internal_request_id
|
36
36
|
end
|
@@ -83,7 +83,21 @@ module ActionDispatch
|
|
83
83
|
include SessionObject
|
84
84
|
|
85
85
|
private
|
86
|
+
def set_cookie(request, session_id, cookie)
|
87
|
+
request.cookie_jar[key] = cookie
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class AbstractSecureStore < Rack::Session::Abstract::PersistedSecure
|
92
|
+
include Compatibility
|
93
|
+
include StaleSessionCheck
|
94
|
+
include SessionObject
|
95
|
+
|
96
|
+
def generate_sid
|
97
|
+
Rack::Session::SessionId.new(super)
|
98
|
+
end
|
86
99
|
|
100
|
+
private
|
87
101
|
def set_cookie(request, session_id, cookie)
|
88
102
|
request.cookie_jar[key] = cookie
|
89
103
|
end
|
@@ -12,7 +12,7 @@ module ActionDispatch
|
|
12
12
|
# * <tt>cache</tt> - The cache to use. If it is not specified, <tt>Rails.cache</tt> will be used.
|
13
13
|
# * <tt>expire_after</tt> - The length of time a session will be stored before automatically expiring.
|
14
14
|
# By default, the <tt>:expires_in</tt> option of the cache is used.
|
15
|
-
class CacheStore <
|
15
|
+
class CacheStore < AbstractSecureStore
|
16
16
|
def initialize(app, options = {})
|
17
17
|
@cache = options[:cache] || Rails.cache
|
18
18
|
options[:expire_after] ||= @cache.options[:expires_in]
|
@@ -21,7 +21,7 @@ module ActionDispatch
|
|
21
21
|
|
22
22
|
# Get a session from the cache.
|
23
23
|
def find_session(env, sid)
|
24
|
-
unless sid && (session =
|
24
|
+
unless sid && (session = get_session_with_fallback(sid))
|
25
25
|
sid, session = generate_sid, {}
|
26
26
|
end
|
27
27
|
[sid, session]
|
@@ -29,7 +29,7 @@ module ActionDispatch
|
|
29
29
|
|
30
30
|
# Set a session in the cache.
|
31
31
|
def write_session(env, sid, session, options)
|
32
|
-
key = cache_key(sid)
|
32
|
+
key = cache_key(sid.private_id)
|
33
33
|
if session
|
34
34
|
@cache.write(key, session, expires_in: options[:expire_after])
|
35
35
|
else
|
@@ -40,14 +40,19 @@ module ActionDispatch
|
|
40
40
|
|
41
41
|
# Remove a session from the cache.
|
42
42
|
def delete_session(env, sid, options)
|
43
|
-
@cache.delete(cache_key(sid))
|
43
|
+
@cache.delete(cache_key(sid.private_id))
|
44
|
+
@cache.delete(cache_key(sid.public_id))
|
44
45
|
generate_sid
|
45
46
|
end
|
46
47
|
|
47
48
|
private
|
48
49
|
# Turn the session id into a cache key.
|
49
|
-
def cache_key(
|
50
|
-
"_session_id:#{
|
50
|
+
def cache_key(id)
|
51
|
+
"_session_id:#{id}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def get_session_with_fallback(sid)
|
55
|
+
@cache.read(cache_key(sid.private_id)) || @cache.read(cache_key(sid.public_id))
|
51
56
|
end
|
52
57
|
end
|
53
58
|
end
|
@@ -16,16 +16,11 @@ module ActionDispatch
|
|
16
16
|
# The cookie jar used for storage is automatically configured to be the
|
17
17
|
# best possible option given your application's configuration.
|
18
18
|
#
|
19
|
-
# If you only have secret_token set, your cookies will be signed, but
|
20
|
-
# not encrypted. This means a user cannot alter their +user_id+ without
|
21
|
-
# knowing your app's secret key, but can easily read their +user_id+. This
|
22
|
-
# was the default for Rails 3 apps.
|
23
|
-
#
|
24
19
|
# Your cookies will be encrypted using your apps secret_key_base. This
|
25
20
|
# goes a step further than signed cookies in that encrypted cookies cannot
|
26
21
|
# be altered or read by users. This is the default starting in Rails 4.
|
27
22
|
#
|
28
|
-
# Configure your session store in
|
23
|
+
# Configure your session store in an initializer:
|
29
24
|
#
|
30
25
|
# Rails.application.config.session_store :cookie_store, key: '_your_app_session'
|
31
26
|
#
|
@@ -51,7 +46,16 @@ module ActionDispatch
|
|
51
46
|
# would set the session cookie to expire automatically 14 days after creation.
|
52
47
|
# Other useful options include <tt>:key</tt>, <tt>:secure</tt> and
|
53
48
|
# <tt>:httponly</tt>.
|
54
|
-
class CookieStore <
|
49
|
+
class CookieStore < AbstractSecureStore
|
50
|
+
class SessionId < DelegateClass(Rack::Session::SessionId)
|
51
|
+
attr_reader :cookie_value
|
52
|
+
|
53
|
+
def initialize(session_id, cookie_value = {})
|
54
|
+
super(session_id)
|
55
|
+
@cookie_value = cookie_value
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
55
59
|
def initialize(app, options = {})
|
56
60
|
super(app, options.merge!(cookie_only: true))
|
57
61
|
end
|
@@ -59,7 +63,7 @@ module ActionDispatch
|
|
59
63
|
def delete_session(req, session_id, options)
|
60
64
|
new_sid = generate_sid unless options[:drop]
|
61
65
|
# Reset hash and Assign the new session id
|
62
|
-
req.set_header("action_dispatch.request.unsigned_session_cookie", new_sid ? { "session_id" => new_sid } : {})
|
66
|
+
req.set_header("action_dispatch.request.unsigned_session_cookie", new_sid ? { "session_id" => new_sid.public_id } : {})
|
63
67
|
new_sid
|
64
68
|
end
|
65
69
|
|
@@ -67,7 +71,7 @@ module ActionDispatch
|
|
67
71
|
stale_session_check! do
|
68
72
|
data = unpacked_cookie_data(req)
|
69
73
|
data = persistent_session_id!(data)
|
70
|
-
[data["session_id"], data]
|
74
|
+
[Rack::Session::SessionId.new(data["session_id"]), data]
|
71
75
|
end
|
72
76
|
end
|
73
77
|
|
@@ -75,7 +79,8 @@ module ActionDispatch
|
|
75
79
|
|
76
80
|
def extract_session_id(req)
|
77
81
|
stale_session_check! do
|
78
|
-
unpacked_cookie_data(req)["session_id"]
|
82
|
+
sid = unpacked_cookie_data(req)["session_id"]
|
83
|
+
sid && Rack::Session::SessionId.new(sid)
|
79
84
|
end
|
80
85
|
end
|
81
86
|
|
@@ -93,13 +98,13 @@ module ActionDispatch
|
|
93
98
|
|
94
99
|
def persistent_session_id!(data, sid = nil)
|
95
100
|
data ||= {}
|
96
|
-
data["session_id"] ||= sid || generate_sid
|
101
|
+
data["session_id"] ||= sid || generate_sid.public_id
|
97
102
|
data
|
98
103
|
end
|
99
104
|
|
100
105
|
def write_session(req, sid, session_data, options)
|
101
|
-
session_data["session_id"] = sid
|
102
|
-
session_data
|
106
|
+
session_data["session_id"] = sid.public_id
|
107
|
+
SessionId.new(sid, session_data)
|
103
108
|
end
|
104
109
|
|
105
110
|
def set_cookie(request, session_id, cookie)
|
@@ -45,7 +45,7 @@ module ActionDispatch
|
|
45
45
|
backtrace_cleaner = request.get_header "action_dispatch.backtrace_cleaner"
|
46
46
|
wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
|
47
47
|
status = wrapper.status_code
|
48
|
-
request.set_header "action_dispatch.exception", wrapper.
|
48
|
+
request.set_header "action_dispatch.exception", wrapper.unwrapped_exception
|
49
49
|
request.set_header "action_dispatch.original_path", request.path_info
|
50
50
|
request.path_info = "/#{status}"
|
51
51
|
response = @exceptions_app.call(request.env)
|
@@ -83,7 +83,7 @@ module ActionDispatch
|
|
83
83
|
|
84
84
|
private
|
85
85
|
def set_hsts_header!(headers)
|
86
|
-
headers["Strict-Transport-Security"
|
86
|
+
headers["Strict-Transport-Security"] ||= @hsts_header
|
87
87
|
end
|
88
88
|
|
89
89
|
def normalize_hsts_options(options)
|
@@ -102,23 +102,23 @@ module ActionDispatch
|
|
102
102
|
|
103
103
|
# https://tools.ietf.org/html/rfc6797#section-6.1
|
104
104
|
def build_hsts_header(hsts)
|
105
|
-
value = "max-age=#{hsts[:expires].to_i}"
|
105
|
+
value = +"max-age=#{hsts[:expires].to_i}"
|
106
106
|
value << "; includeSubDomains" if hsts[:subdomains]
|
107
107
|
value << "; preload" if hsts[:preload]
|
108
108
|
value
|
109
109
|
end
|
110
110
|
|
111
111
|
def flag_cookies_as_secure!(headers)
|
112
|
-
if cookies = headers["Set-Cookie"
|
113
|
-
cookies = cookies.split("\n"
|
112
|
+
if cookies = headers["Set-Cookie"]
|
113
|
+
cookies = cookies.split("\n")
|
114
114
|
|
115
|
-
headers["Set-Cookie"
|
116
|
-
if
|
115
|
+
headers["Set-Cookie"] = cookies.map { |cookie|
|
116
|
+
if !/;\s*secure\s*(;|$)/i.match?(cookie)
|
117
117
|
"#{cookie}; secure"
|
118
118
|
else
|
119
119
|
cookie
|
120
120
|
end
|
121
|
-
}.join("\n"
|
121
|
+
}.join("\n")
|
122
122
|
end
|
123
123
|
end
|
124
124
|
|
@@ -141,7 +141,7 @@ module ActionDispatch
|
|
141
141
|
host = @redirect[:host] || request.host
|
142
142
|
port = @redirect[:port] || request.port
|
143
143
|
|
144
|
-
location = "https://#{host}"
|
144
|
+
location = +"https://#{host}"
|
145
145
|
location << ":#{port}" if port != 80 && port != 443
|
146
146
|
location << request.fullpath
|
147
147
|
location
|
@@ -36,6 +36,31 @@ module ActionDispatch
|
|
36
36
|
def build(app)
|
37
37
|
klass.new(app, *args, &block)
|
38
38
|
end
|
39
|
+
|
40
|
+
def build_instrumented(app)
|
41
|
+
InstrumentationProxy.new(build(app), inspect)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# This class is used to instrument the execution of a single middleware.
|
46
|
+
# It proxies the `call` method transparently and instruments the method
|
47
|
+
# call.
|
48
|
+
class InstrumentationProxy
|
49
|
+
EVENT_NAME = "process_middleware.action_dispatch"
|
50
|
+
|
51
|
+
def initialize(middleware, class_name)
|
52
|
+
@middleware = middleware
|
53
|
+
|
54
|
+
@payload = {
|
55
|
+
middleware: class_name,
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
def call(env)
|
60
|
+
ActiveSupport::Notifications.instrument(EVENT_NAME, @payload) do
|
61
|
+
@middleware.call(env)
|
62
|
+
end
|
63
|
+
end
|
39
64
|
end
|
40
65
|
|
41
66
|
include Enumerable
|
@@ -98,7 +123,14 @@ module ActionDispatch
|
|
98
123
|
end
|
99
124
|
|
100
125
|
def build(app = nil, &block)
|
101
|
-
|
126
|
+
instrumenting = ActiveSupport::Notifications.notifier.listening?(InstrumentationProxy::EVENT_NAME)
|
127
|
+
middlewares.freeze.reverse.inject(app || block) do |a, e|
|
128
|
+
if instrumenting
|
129
|
+
e.build_instrumented(a)
|
130
|
+
else
|
131
|
+
e.build(a)
|
132
|
+
end
|
133
|
+
end
|
102
134
|
end
|
103
135
|
|
104
136
|
private
|
@@ -41,7 +41,6 @@ module ActionDispatch
|
|
41
41
|
rescue SystemCallError
|
42
42
|
false
|
43
43
|
end
|
44
|
-
|
45
44
|
}
|
46
45
|
return ::Rack::Utils.escape_path(match).b
|
47
46
|
end
|
@@ -69,7 +68,7 @@ module ActionDispatch
|
|
69
68
|
|
70
69
|
headers["Vary"] = "Accept-Encoding" if gzip_path
|
71
70
|
|
72
|
-
|
71
|
+
[status, headers, body]
|
73
72
|
ensure
|
74
73
|
request.path_info = path
|
75
74
|
end
|
@@ -80,7 +79,7 @@ module ActionDispatch
|
|
80
79
|
end
|
81
80
|
|
82
81
|
def content_type(path)
|
83
|
-
::Rack::Mime.mime_type(::File.extname(path), "text/plain"
|
82
|
+
::Rack::Mime.mime_type(::File.extname(path), "text/plain")
|
84
83
|
end
|
85
84
|
|
86
85
|
def gzip_encoding_accepted?(request)
|
@@ -90,8 +89,8 @@ module ActionDispatch
|
|
90
89
|
def gzip_file_path(path)
|
91
90
|
can_gzip_mime = content_type(path) =~ /\A(?:text\/|application\/javascript)/
|
92
91
|
gzip_path = "#{path}.gz"
|
93
|
-
if can_gzip_mime && File.exist?(File.join(@root, ::Rack::Utils.unescape_path(gzip_path)
|
94
|
-
gzip_path
|
92
|
+
if can_gzip_mime && File.exist?(File.join(@root, ::Rack::Utils.unescape_path(gzip_path)))
|
93
|
+
gzip_path
|
95
94
|
else
|
96
95
|
false
|
97
96
|
end
|
@@ -117,7 +116,7 @@ module ActionDispatch
|
|
117
116
|
req = Rack::Request.new env
|
118
117
|
|
119
118
|
if req.get? || req.head?
|
120
|
-
path = req.path_info.chomp("/"
|
119
|
+
path = req.path_info.chomp("/")
|
121
120
|
if match = @file_handler.match?(path)
|
122
121
|
req.path_info = match
|
123
122
|
return @file_handler.serve(req)
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<% actions = ActiveSupport::ActionableError.actions(exception) %>
|
2
|
+
|
3
|
+
<% if actions.any? %>
|
4
|
+
<div class="actions">
|
5
|
+
<% actions.each do |action, _| %>
|
6
|
+
<%= button_to action, ActionDispatch::ActionableExceptions.endpoint, params: {
|
7
|
+
error: exception.class.name,
|
8
|
+
action: action,
|
9
|
+
location: request.path
|
10
|
+
} %>
|
11
|
+
<% end %>
|
12
|
+
</div>
|
13
|
+
<% end %>
|
File without changes
|
@@ -6,7 +6,9 @@
|
|
6
6
|
<% end %>
|
7
7
|
|
8
8
|
<h2 style="margin-top: 30px">Request</h2>
|
9
|
-
|
9
|
+
<% if params_valid? %>
|
10
|
+
<p><b>Parameters</b>:</p> <pre><%= debug_params(@request.filtered_parameters) %></pre>
|
11
|
+
<% end %>
|
10
12
|
|
11
13
|
<div class="details">
|
12
14
|
<div class="summary"><a href="#" onclick="return toggleSessionDump()">Toggle session dump</a></div>
|
@@ -1,6 +1,8 @@
|
|
1
|
-
<%
|
1
|
+
<% error_index = local_assigns[:error_index] || 0 %>
|
2
|
+
|
3
|
+
<% source_extracts.each_with_index do |source_extract, index| %>
|
2
4
|
<% if source_extract[:code] %>
|
3
|
-
<div class="source <%="hidden" if
|
5
|
+
<div class="source <%= "hidden" if show_source_idx != index %>" id="frame-source-<%= error_index %>-<%= index %>">
|
4
6
|
<div class="info">
|
5
7
|
Extracted source (around line <strong>#<%= source_extract[:line_number] %></strong>):
|
6
8
|
</div>
|
@@ -1,52 +1,62 @@
|
|
1
|
-
<% names =
|
1
|
+
<% names = traces.keys %>
|
2
|
+
<% error_index = local_assigns[:error_index] || 0 %>
|
2
3
|
|
3
4
|
<p><code>Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %></code></p>
|
4
5
|
|
5
|
-
<div id="traces">
|
6
|
+
<div id="traces-<%= error_index %>">
|
6
7
|
<% names.each do |name| %>
|
7
8
|
<%
|
8
|
-
show = "show('#{name.gsub(/\s/, '-')}');"
|
9
|
-
hide = (names - [name]).collect {|hide_name| "hide('#{hide_name.gsub(/\s/, '-')}');"}
|
9
|
+
show = "show('#{name.gsub(/\s/, '-')}-#{error_index}');"
|
10
|
+
hide = (names - [name]).collect {|hide_name| "hide('#{hide_name.gsub(/\s/, '-')}-#{error_index}');"}
|
10
11
|
%>
|
11
12
|
<a href="#" onclick="<%= hide.join %><%= show %>; return false;"><%= name %></a> <%= '|' unless names.last == name %>
|
12
13
|
<% end %>
|
13
14
|
|
14
|
-
<%
|
15
|
-
<div id="<%= name.gsub(/\s/, '-') %>" style="display: <%= (name ==
|
16
|
-
<
|
15
|
+
<% traces.each do |name, trace| %>
|
16
|
+
<div id="<%= "#{name.gsub(/\s/, '-')}-#{error_index}" %>" style="display: <%= (name == trace_to_show) ? 'block' : 'none' %>;">
|
17
|
+
<code style="font-size: 11px;">
|
18
|
+
<% trace.each do |frame| %>
|
19
|
+
<a class="trace-frames trace-frames-<%= error_index %>" data-exception-object-id="<%= frame[:exception_object_id] %>" data-frame-id="<%= frame[:id] %>" href="#">
|
20
|
+
<%= frame[:trace] %>
|
21
|
+
</a>
|
22
|
+
<br>
|
23
|
+
<% end %>
|
24
|
+
</code>
|
17
25
|
</div>
|
18
26
|
<% end %>
|
19
27
|
|
20
28
|
<script type="text/javascript">
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
traceFrames
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
currentSource
|
46
|
-
|
47
|
-
|
29
|
+
(function() {
|
30
|
+
var traceFrames = document.getElementsByClassName('trace-frames-<%= error_index %>');
|
31
|
+
var selectedFrame, currentSource = document.getElementById('frame-source-<%= error_index %>-0');
|
32
|
+
|
33
|
+
// Add click listeners for all stack frames
|
34
|
+
for (var i = 0; i < traceFrames.length; i++) {
|
35
|
+
traceFrames[i].addEventListener('click', function(e) {
|
36
|
+
e.preventDefault();
|
37
|
+
var target = e.target;
|
38
|
+
var frame_id = target.dataset.frameId;
|
39
|
+
|
40
|
+
if (selectedFrame) {
|
41
|
+
selectedFrame.className = selectedFrame.className.replace("selected", "");
|
42
|
+
}
|
43
|
+
|
44
|
+
target.className += " selected";
|
45
|
+
selectedFrame = target;
|
46
|
+
|
47
|
+
// Change the extracted source code
|
48
|
+
changeSourceExtract(frame_id);
|
49
|
+
});
|
50
|
+
|
51
|
+
function changeSourceExtract(frame_id) {
|
52
|
+
var el = document.getElementById('frame-source-<%= error_index %>-' + frame_id);
|
53
|
+
if (currentSource && el) {
|
54
|
+
currentSource.className += " hidden";
|
55
|
+
el.className = el.className.replace(" hidden", "");
|
56
|
+
currentSource = el;
|
57
|
+
}
|
48
58
|
}
|
49
59
|
}
|
50
|
-
}
|
60
|
+
})();
|
51
61
|
</script>
|
52
62
|
</div>
|