actionpack 6.0.0.beta2 → 6.0.2.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 +116 -3
- data/README.rdoc +2 -1
- data/lib/action_controller.rb +4 -1
- data/lib/action_controller/metal.rb +3 -3
- data/lib/action_controller/metal/exceptions.rb +1 -1
- data/lib/action_controller/metal/helpers.rb +1 -1
- data/lib/action_controller/metal/live.rb +1 -1
- data/lib/action_controller/metal/mime_responds.rb +1 -1
- data/lib/action_controller/metal/params_wrapper.rb +2 -2
- data/lib/action_controller/metal/renderers.rb +4 -4
- data/lib/action_controller/metal/rendering.rb +1 -1
- data/lib/action_controller/metal/request_forgery_protection.rb +2 -2
- data/lib/action_controller/metal/strong_parameters.rb +5 -11
- data/lib/action_controller/renderer.rb +2 -2
- data/lib/action_controller/template_assertions.rb +1 -1
- data/lib/action_controller/test_case.rb +3 -2
- data/lib/action_dispatch.rb +1 -1
- data/lib/action_dispatch/http/content_security_policy.rb +20 -9
- data/lib/action_dispatch/http/mime_negotiation.rb +5 -0
- data/lib/action_dispatch/http/mime_type.rb +13 -1
- data/lib/action_dispatch/http/request.rb +2 -1
- data/lib/action_dispatch/http/response.rb +27 -7
- data/lib/action_dispatch/journey/formatter.rb +2 -2
- data/lib/action_dispatch/journey/path/pattern.rb +6 -1
- data/lib/action_dispatch/journey/route.rb +5 -4
- data/lib/action_dispatch/journey/routes.rb +0 -1
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +39 -0
- data/lib/action_dispatch/middleware/cookies.rb +7 -3
- data/lib/action_dispatch/middleware/debug_exceptions.rb +8 -2
- data/lib/action_dispatch/middleware/debug_view.rb +12 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +1 -0
- data/lib/action_dispatch/middleware/public_exceptions.rb +6 -2
- data/lib/action_dispatch/middleware/remote_ip.rb +3 -3
- data/lib/action_dispatch/middleware/session/cookie_store.rb +4 -3
- data/lib/action_dispatch/middleware/stack.rb +34 -2
- 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/diagnostics.html.erb +6 -2
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +2 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +4 -0
- data/lib/action_dispatch/railtie.rb +6 -2
- data/lib/action_dispatch/routing.rb +4 -4
- data/lib/action_dispatch/routing/mapper.rb +28 -13
- data/lib/action_dispatch/routing/route_set.rb +13 -15
- data/lib/action_dispatch/system_test_case.rb +22 -3
- data/lib/action_dispatch/system_testing/browser.rb +23 -0
- data/lib/action_dispatch/system_testing/driver.rb +2 -0
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +2 -1
- 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/request_encoder.rb +2 -2
- data/lib/action_dispatch/testing/test_response.rb +1 -1
- data/lib/action_pack/gem_version.rb +2 -2
- metadata +20 -15
- data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +0 -26
@@ -170,6 +170,7 @@ module Mime
|
|
170
170
|
def parse(accept_header)
|
171
171
|
if !accept_header.include?(",")
|
172
172
|
accept_header = accept_header.split(PARAMETER_SEPARATOR_REGEXP).first
|
173
|
+
return [] unless accept_header
|
173
174
|
parse_trailing_star(accept_header) || [Mime::Type.lookup(accept_header)].compact
|
174
175
|
else
|
175
176
|
list, index = [], 0
|
@@ -221,7 +222,18 @@ module Mime
|
|
221
222
|
|
222
223
|
attr_reader :hash
|
223
224
|
|
225
|
+
MIME_NAME = "[a-zA-Z0-9][a-zA-Z0-9#{Regexp.escape('!#$&-^_.+')}]{0,126}"
|
226
|
+
MIME_PARAMETER_KEY = "[a-zA-Z0-9][a-zA-Z0-9#{Regexp.escape('!#$&-^_.+')}]{0,126}"
|
227
|
+
MIME_PARAMETER_VALUE = "#{Regexp.escape('"')}?[a-zA-Z0-9][a-zA-Z0-9#{Regexp.escape('!#$&-^_.+')}]{0,126}#{Regexp.escape('"')}?"
|
228
|
+
MIME_PARAMETER = "\s*\;\s+#{MIME_PARAMETER_KEY}(?:\=#{MIME_PARAMETER_VALUE})?"
|
229
|
+
MIME_REGEXP = /\A(?:\*\/\*|#{MIME_NAME}\/(?:\*|#{MIME_NAME})(?:\s*#{MIME_PARAMETER}\s*)*)\z/
|
230
|
+
|
231
|
+
class InvalidMimeType < StandardError; end
|
232
|
+
|
224
233
|
def initialize(string, symbol = nil, synonyms = [])
|
234
|
+
if string.nil? || ! MIME_REGEXP.match?(string)
|
235
|
+
raise InvalidMimeType, "#{string.inspect} is not a valid MIME type"
|
236
|
+
end
|
225
237
|
@symbol, @synonyms = symbol, synonyms
|
226
238
|
@string = string
|
227
239
|
@hash = [@string, @synonyms, @symbol].hash
|
@@ -303,7 +315,7 @@ module Mime
|
|
303
315
|
include Singleton
|
304
316
|
|
305
317
|
def initialize
|
306
|
-
super "*/*",
|
318
|
+
super "*/*", nil
|
307
319
|
end
|
308
320
|
|
309
321
|
def all?; true; end
|
@@ -264,7 +264,8 @@ module ActionDispatch
|
|
264
264
|
# (case-insensitive), which may need to be manually added depending on the
|
265
265
|
# choice of JavaScript libraries and frameworks.
|
266
266
|
def xml_http_request?
|
267
|
-
get_header("HTTP_X_REQUESTED_WITH")
|
267
|
+
header = get_header("HTTP_X_REQUESTED_WITH")
|
268
|
+
header && /XMLHttpRequest/i.match?(header)
|
268
269
|
end
|
269
270
|
alias :xhr? :xml_http_request?
|
270
271
|
|
@@ -85,6 +85,7 @@ module ActionDispatch # :nodoc:
|
|
85
85
|
|
86
86
|
cattr_accessor :default_charset, default: "utf-8"
|
87
87
|
cattr_accessor :default_headers
|
88
|
+
cattr_accessor :return_only_media_type_on_content_type, default: false
|
88
89
|
|
89
90
|
include Rack::Response::Helpers
|
90
91
|
# Aliasing these off because AD::Http::Cache::Response defines them.
|
@@ -242,8 +243,22 @@ module ActionDispatch # :nodoc:
|
|
242
243
|
end
|
243
244
|
|
244
245
|
# Content type of response.
|
245
|
-
# It returns just MIME type and does NOT contain charset part.
|
246
246
|
def content_type
|
247
|
+
if self.class.return_only_media_type_on_content_type
|
248
|
+
ActiveSupport::Deprecation.warn(
|
249
|
+
"Rails 6.1 will return Content-Type header without modification." \
|
250
|
+
" If you want just the MIME type, please use `#media_type` instead."
|
251
|
+
)
|
252
|
+
|
253
|
+
content_type = super
|
254
|
+
content_type ? content_type.split(/;\s*charset=/)[0].presence : content_type
|
255
|
+
else
|
256
|
+
super.presence
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
# Media type of response.
|
261
|
+
def media_type
|
247
262
|
parsed_content_type_header.mime_type
|
248
263
|
end
|
249
264
|
|
@@ -404,15 +419,18 @@ module ActionDispatch # :nodoc:
|
|
404
419
|
end
|
405
420
|
|
406
421
|
private
|
407
|
-
|
408
422
|
ContentTypeHeader = Struct.new :mime_type, :charset
|
409
423
|
NullContentTypeHeader = ContentTypeHeader.new nil, nil
|
410
424
|
|
425
|
+
CONTENT_TYPE_PARSER = /
|
426
|
+
\A
|
427
|
+
(?<mime_type>[^;\s]+\s*(?:;\s*(?:(?!charset)[^;\s])+)*)?
|
428
|
+
(?:;\s*charset=(?<quote>"?)(?<charset>[^;\s]+)\k<quote>)?
|
429
|
+
/x # :nodoc:
|
430
|
+
|
411
431
|
def parse_content_type(content_type)
|
412
|
-
if content_type
|
413
|
-
|
414
|
-
type = nil if type && type.empty?
|
415
|
-
ContentTypeHeader.new(type, charset)
|
432
|
+
if content_type && match = CONTENT_TYPE_PARSER.match(content_type)
|
433
|
+
ContentTypeHeader.new(match[:mime_type], match[:charset])
|
416
434
|
else
|
417
435
|
NullContentTypeHeader
|
418
436
|
end
|
@@ -459,7 +477,7 @@ module ActionDispatch # :nodoc:
|
|
459
477
|
end
|
460
478
|
|
461
479
|
def assign_default_content_type_and_charset!
|
462
|
-
return if
|
480
|
+
return if media_type
|
463
481
|
|
464
482
|
ct = parsed_content_type_header
|
465
483
|
set_content_type(ct.mime_type || Mime[:html].to_s,
|
@@ -517,4 +535,6 @@ module ActionDispatch # :nodoc:
|
|
517
535
|
end
|
518
536
|
end
|
519
537
|
end
|
538
|
+
|
539
|
+
ActiveSupport.run_load_hooks(:action_dispatch_response, Response)
|
520
540
|
end
|
@@ -67,7 +67,7 @@ module ActionDispatch
|
|
67
67
|
parameterized_parts = recall.merge(options)
|
68
68
|
|
69
69
|
keys_to_keep = route.parts.reverse_each.drop_while { |part|
|
70
|
-
!options.key?(part) || (options[part] || recall[part]).nil?
|
70
|
+
!(options.key?(part) || route.scope_options.key?(part)) || (options[part] || recall[part]).nil?
|
71
71
|
} | route.required_parts
|
72
72
|
|
73
73
|
parameterized_parts.delete_if do |bad_key, _|
|
@@ -152,7 +152,7 @@ module ActionDispatch
|
|
152
152
|
missing_keys << key
|
153
153
|
end
|
154
154
|
else
|
155
|
-
|
155
|
+
if parts[key].nil? || !/\A#{tests[key]}\Z/.match?(parts[key])
|
156
156
|
missing_keys ||= []
|
157
157
|
missing_keys << key
|
158
158
|
end
|
@@ -119,7 +119,8 @@ module ActionDispatch
|
|
119
119
|
|
120
120
|
class UnanchoredRegexp < AnchoredRegexp # :nodoc:
|
121
121
|
def accept(node)
|
122
|
-
|
122
|
+
path = visit node
|
123
|
+
path == "/" ? %r{\A/} : %r{\A#{path}(?:\b|\Z|/)}
|
123
124
|
end
|
124
125
|
end
|
125
126
|
|
@@ -136,6 +137,10 @@ module ActionDispatch
|
|
136
137
|
Array.new(length - 1) { |i| self[i + 1] }
|
137
138
|
end
|
138
139
|
|
140
|
+
def named_captures
|
141
|
+
@names.zip(captures).to_h
|
142
|
+
end
|
143
|
+
|
139
144
|
def [](x)
|
140
145
|
idx = @offsets[x - 1] + x
|
141
146
|
@match[idx]
|
@@ -4,9 +4,9 @@ module ActionDispatch
|
|
4
4
|
# :stopdoc:
|
5
5
|
module Journey
|
6
6
|
class Route
|
7
|
-
attr_reader :app, :path, :defaults, :name, :precedence
|
7
|
+
attr_reader :app, :path, :defaults, :name, :precedence, :constraints,
|
8
|
+
:internal, :scope_options
|
8
9
|
|
9
|
-
attr_reader :constraints, :internal
|
10
10
|
alias :conditions :constraints
|
11
11
|
|
12
12
|
module VerbMatchers
|
@@ -51,13 +51,13 @@ module ActionDispatch
|
|
51
51
|
|
52
52
|
def self.build(name, app, path, constraints, required_defaults, defaults)
|
53
53
|
request_method_match = verb_matcher(constraints.delete(:request_method))
|
54
|
-
new name, app, path, constraints, required_defaults, defaults, request_method_match, 0
|
54
|
+
new name, app, path, constraints, required_defaults, defaults, request_method_match, 0, {}
|
55
55
|
end
|
56
56
|
|
57
57
|
##
|
58
58
|
# +path+ is a path constraint.
|
59
59
|
# +constraints+ is a hash of constraints to be applied to this route.
|
60
|
-
def initialize(name, app, path, constraints, required_defaults, defaults, request_method_match, precedence, internal = false)
|
60
|
+
def initialize(name, app, path, constraints, required_defaults, defaults, request_method_match, precedence, scope_options, internal = false)
|
61
61
|
@name = name
|
62
62
|
@app = app
|
63
63
|
@path = path
|
@@ -72,6 +72,7 @@ module ActionDispatch
|
|
72
72
|
@decorated_ast = nil
|
73
73
|
@precedence = precedence
|
74
74
|
@path_formatter = @path.build_formatter
|
75
|
+
@scope_options = scope_options
|
75
76
|
@internal = internal
|
76
77
|
end
|
77
78
|
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "erb"
|
4
|
+
require "action_dispatch/http/request"
|
5
|
+
require "active_support/actionable_error"
|
6
|
+
|
7
|
+
module ActionDispatch
|
8
|
+
class ActionableExceptions # :nodoc:
|
9
|
+
cattr_accessor :endpoint, default: "/rails/actions"
|
10
|
+
|
11
|
+
def initialize(app)
|
12
|
+
@app = app
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
request = ActionDispatch::Request.new(env)
|
17
|
+
return @app.call(env) unless actionable_request?(request)
|
18
|
+
|
19
|
+
ActiveSupport::ActionableError.dispatch(request.params[:error].to_s.safe_constantize, request.params[:action])
|
20
|
+
|
21
|
+
redirect_to request.params[:location]
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def actionable_request?(request)
|
26
|
+
request.show_exceptions? && request.post? && request.path == endpoint
|
27
|
+
end
|
28
|
+
|
29
|
+
def redirect_to(location)
|
30
|
+
body = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(location)}\">redirected</a>.</body></html>"
|
31
|
+
|
32
|
+
[302, {
|
33
|
+
"Content-Type" => "text/html; charset=#{Response.default_charset}",
|
34
|
+
"Content-Length" => body.bytesize.to_s,
|
35
|
+
"Location" => location,
|
36
|
+
}, [body]]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -338,7 +338,7 @@ module ActionDispatch
|
|
338
338
|
|
339
339
|
def update_cookies_from_jar
|
340
340
|
request_jar = @request.cookie_jar.instance_variable_get(:@cookies)
|
341
|
-
set_cookies = request_jar.reject { |k, _| @delete_cookies.key?(k) }
|
341
|
+
set_cookies = request_jar.reject { |k, _| @delete_cookies.key?(k) || @set_cookies.key?(k) }
|
342
342
|
|
343
343
|
@cookies.update set_cookies if set_cookies
|
344
344
|
end
|
@@ -534,9 +534,13 @@ module ActionDispatch
|
|
534
534
|
if value
|
535
535
|
case
|
536
536
|
when needs_migration?(value)
|
537
|
-
|
537
|
+
Marshal.load(value).tap do |v|
|
538
|
+
self[name] = { value: v }
|
539
|
+
end
|
538
540
|
when rotate
|
539
|
-
|
541
|
+
serializer.load(value).tap do |v|
|
542
|
+
self[name] = { value: v }
|
543
|
+
end
|
540
544
|
else
|
541
545
|
serializer.load(value)
|
542
546
|
end
|
@@ -4,6 +4,8 @@ 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
|
+
|
7
9
|
require "action_view"
|
8
10
|
require "action_view/base"
|
9
11
|
|
@@ -60,7 +62,11 @@ module ActionDispatch
|
|
60
62
|
log_error(request, wrapper)
|
61
63
|
|
62
64
|
if request.get_header("action_dispatch.show_detailed_exceptions")
|
63
|
-
|
65
|
+
begin
|
66
|
+
content_type = request.formats.first
|
67
|
+
rescue Mime::Type::InvalidMimeType
|
68
|
+
render_for_api_request(Mime[:text], wrapper)
|
69
|
+
end
|
64
70
|
|
65
71
|
if api_request?(content_type)
|
66
72
|
render_for_api_request(content_type, wrapper)
|
@@ -142,7 +148,7 @@ module ActionDispatch
|
|
142
148
|
message = []
|
143
149
|
message << " "
|
144
150
|
message << "#{exception.class} (#{exception.message}):"
|
145
|
-
message.concat(exception.
|
151
|
+
message.concat(exception.annotated_source_code) if exception.respond_to?(:annotated_source_code)
|
146
152
|
message << " "
|
147
153
|
message.concat(trace)
|
148
154
|
|
@@ -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
|
+
"Mime::Type::InvalidMimeType" => :not_acceptable,
|
15
16
|
"ActionController::MissingExactTemplate" => :not_acceptable,
|
16
17
|
"ActionController::InvalidAuthenticityToken" => :unprocessable_entity,
|
17
18
|
"ActionController::InvalidCrossOriginRequest" => :unprocessable_entity,
|
@@ -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
|
#
|
@@ -24,9 +24,10 @@ module ActionDispatch
|
|
24
24
|
#
|
25
25
|
# Rails.application.config.session_store :cookie_store, key: '_your_app_session'
|
26
26
|
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
# encrypted in the
|
27
|
+
# In the development and test environments your application's secret key base is
|
28
|
+
# generated by Rails and stored in a temporary file in <tt>tmp/development_secret.txt</tt>.
|
29
|
+
# In all other environments, it is stored encrypted in the
|
30
|
+
# <tt>config/credentials.yml.enc</tt> file.
|
30
31
|
#
|
31
32
|
# If your application was not updated to Rails 5.2 defaults, the secret_key_base
|
32
33
|
# will be found in the old <tt>config/secrets.yml</tt> file.
|
@@ -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
|
@@ -97,8 +122,15 @@ module ActionDispatch
|
|
97
122
|
middlewares.push(build_middleware(klass, args, block))
|
98
123
|
end
|
99
124
|
|
100
|
-
def build(app =
|
101
|
-
|
125
|
+
def build(app = nil, &block)
|
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
|
@@ -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 %>
|