actionpack 5.2.4.4 → 6.1.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 +264 -322
- data/MIT-LICENSE +1 -1
- data/README.rdoc +4 -3
- data/lib/abstract_controller.rb +1 -0
- data/lib/abstract_controller/base.rb +38 -4
- data/lib/abstract_controller/caching.rb +1 -1
- data/lib/abstract_controller/caching/fragments.rb +6 -22
- data/lib/abstract_controller/callbacks.rb +14 -2
- data/lib/abstract_controller/collector.rb +1 -2
- data/lib/abstract_controller/helpers.rb +106 -90
- data/lib/abstract_controller/railties/routes_helpers.rb +1 -1
- data/lib/abstract_controller/rendering.rb +9 -9
- data/lib/abstract_controller/translation.rb +11 -5
- data/lib/action_controller.rb +7 -4
- data/lib/action_controller/api.rb +4 -3
- data/lib/action_controller/base.rb +6 -9
- data/lib/action_controller/caching.rb +1 -3
- data/lib/action_controller/log_subscriber.rb +10 -7
- data/lib/action_controller/metal.rb +10 -8
- data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
- data/lib/action_controller/metal/conditional_get.rb +19 -5
- data/lib/action_controller/metal/content_security_policy.rb +1 -2
- data/lib/action_controller/metal/cookies.rb +3 -1
- data/lib/action_controller/metal/data_streaming.rb +6 -7
- data/lib/action_controller/metal/default_headers.rb +17 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +3 -5
- data/lib/action_controller/metal/exceptions.rb +56 -2
- data/lib/action_controller/metal/flash.rb +5 -5
- data/lib/action_controller/metal/head.rb +7 -4
- data/lib/action_controller/metal/helpers.rb +14 -5
- data/lib/action_controller/metal/http_authentication.rb +24 -23
- data/lib/action_controller/metal/implicit_render.rb +5 -15
- data/lib/action_controller/metal/instrumentation.rb +13 -14
- data/lib/action_controller/metal/live.rb +30 -32
- data/lib/action_controller/metal/logging.rb +20 -0
- data/lib/action_controller/metal/mime_responds.rb +19 -4
- data/lib/action_controller/metal/parameter_encoding.rb +35 -4
- data/lib/action_controller/metal/params_wrapper.rb +31 -22
- data/lib/action_controller/metal/permissions_policy.rb +46 -0
- data/lib/action_controller/metal/redirecting.rb +6 -6
- data/lib/action_controller/metal/renderers.rb +4 -4
- data/lib/action_controller/metal/rendering.rb +8 -3
- data/lib/action_controller/metal/request_forgery_protection.rb +62 -34
- data/lib/action_controller/metal/rescue.rb +1 -1
- data/lib/action_controller/metal/streaming.rb +0 -1
- data/lib/action_controller/metal/strong_parameters.rb +167 -58
- 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 +37 -13
- data/lib/action_controller/template_assertions.rb +1 -1
- data/lib/action_controller/test_case.rb +70 -65
- data/lib/action_dispatch.rb +9 -3
- data/lib/action_dispatch/http/cache.rb +26 -21
- data/lib/action_dispatch/http/content_disposition.rb +45 -0
- data/lib/action_dispatch/http/content_security_policy.rb +33 -19
- data/lib/action_dispatch/http/filter_parameters.rb +9 -8
- data/lib/action_dispatch/http/filter_redirect.rb +2 -3
- data/lib/action_dispatch/http/headers.rb +4 -4
- data/lib/action_dispatch/http/mime_negotiation.rb +26 -13
- data/lib/action_dispatch/http/mime_type.rb +42 -23
- data/lib/action_dispatch/http/parameters.rb +14 -23
- data/lib/action_dispatch/http/permissions_policy.rb +173 -0
- data/lib/action_dispatch/http/request.rb +45 -22
- data/lib/action_dispatch/http/response.rb +45 -25
- data/lib/action_dispatch/http/upload.rb +9 -1
- data/lib/action_dispatch/http/url.rb +82 -82
- data/lib/action_dispatch/journey.rb +0 -2
- data/lib/action_dispatch/journey/formatter.rb +54 -30
- data/lib/action_dispatch/journey/gtg/builder.rb +22 -37
- data/lib/action_dispatch/journey/gtg/simulator.rb +8 -7
- data/lib/action_dispatch/journey/gtg/transition_table.rb +6 -5
- data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
- data/lib/action_dispatch/journey/nodes/node.rb +13 -11
- 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 +19 -21
- data/lib/action_dispatch/journey/route.rb +10 -20
- data/lib/action_dispatch/journey/router.rb +26 -34
- data/lib/action_dispatch/journey/router/utils.rb +14 -12
- data/lib/action_dispatch/journey/routes.rb +0 -2
- data/lib/action_dispatch/journey/scanner.rb +10 -4
- data/lib/action_dispatch/journey/visitors.rb +1 -4
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
- data/lib/action_dispatch/middleware/callbacks.rb +2 -4
- data/lib/action_dispatch/middleware/cookies.rb +128 -109
- data/lib/action_dispatch/middleware/debug_exceptions.rb +43 -66
- data/lib/action_dispatch/middleware/debug_locks.rb +5 -5
- data/lib/action_dispatch/middleware/debug_view.rb +66 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +75 -30
- data/lib/action_dispatch/middleware/flash.rb +1 -1
- data/lib/action_dispatch/middleware/host_authorization.rb +121 -0
- data/lib/action_dispatch/middleware/public_exceptions.rb +6 -3
- data/lib/action_dispatch/middleware/remote_ip.rb +14 -16
- data/lib/action_dispatch/middleware/request_id.rb +5 -6
- data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -3
- data/lib/action_dispatch/middleware/session/cookie_store.rb +3 -9
- data/lib/action_dispatch/middleware/show_exceptions.rb +3 -2
- data/lib/action_dispatch/middleware/ssl.rb +20 -15
- data/lib/action_dispatch/middleware/stack.rb +56 -2
- data/lib/action_dispatch/middleware/static.rb +153 -93
- 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/_message_and_suggestions.html.erb +22 -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 +23 -4
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +6 -3
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +3 -1
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +104 -8
- 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/rescues/unknown_action.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +24 -1
- data/lib/action_dispatch/railtie.rb +8 -2
- data/lib/action_dispatch/request/session.rb +10 -9
- data/lib/action_dispatch/request/utils.rb +26 -2
- data/lib/action_dispatch/routing.rb +21 -20
- data/lib/action_dispatch/routing/inspector.rb +100 -52
- data/lib/action_dispatch/routing/mapper.rb +155 -103
- data/lib/action_dispatch/routing/polymorphic_routes.rb +13 -15
- data/lib/action_dispatch/routing/redirection.rb +3 -3
- data/lib/action_dispatch/routing/route_set.rb +71 -69
- data/lib/action_dispatch/routing/url_for.rb +2 -2
- data/lib/action_dispatch/system_test_case.rb +54 -11
- data/lib/action_dispatch/system_testing/browser.rb +53 -16
- data/lib/action_dispatch/system_testing/driver.rb +11 -3
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +49 -7
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +8 -10
- data/lib/action_dispatch/testing/assertion_response.rb +0 -1
- data/lib/action_dispatch/testing/assertions.rb +1 -1
- data/lib/action_dispatch/testing/assertions/response.rb +4 -7
- data/lib/action_dispatch/testing/assertions/routing.rb +20 -8
- data/lib/action_dispatch/testing/integration.rb +61 -28
- data/lib/action_dispatch/testing/request_encoder.rb +2 -2
- data/lib/action_dispatch/testing/test_process.rb +29 -4
- data/lib/action_dispatch/testing/test_request.rb +3 -3
- 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 +38 -26
- data/lib/action_controller/metal/force_ssl.rb +0 -99
- data/lib/action_dispatch/http/parameter_filter.rb +0 -86
- data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
- data/lib/action_dispatch/journey/nfa/simulator.rb +0 -49
- data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -120
- data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +0 -26
@@ -3,57 +3,25 @@
|
|
3
3
|
require "action_dispatch/http/request"
|
4
4
|
require "action_dispatch/middleware/exception_wrapper"
|
5
5
|
require "action_dispatch/routing/inspector"
|
6
|
-
require "action_view"
|
7
|
-
require "action_view/base"
|
8
6
|
|
9
|
-
require "
|
7
|
+
require "action_view"
|
10
8
|
|
11
9
|
module ActionDispatch
|
12
10
|
# This middleware is responsible for logging exceptions and
|
13
11
|
# showing a debugging page in case the request is local.
|
14
12
|
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
|
13
|
+
cattr_reader :interceptors, instance_accessor: false, default: []
|
44
14
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
super
|
49
|
-
end
|
50
|
-
end
|
15
|
+
def self.register_interceptor(object = nil, &block)
|
16
|
+
interceptor = object || block
|
17
|
+
interceptors << interceptor
|
51
18
|
end
|
52
19
|
|
53
|
-
def initialize(app, routes_app = nil, response_format = :default)
|
20
|
+
def initialize(app, routes_app = nil, response_format = :default, interceptors = self.class.interceptors)
|
54
21
|
@app = app
|
55
22
|
@routes_app = routes_app
|
56
23
|
@response_format = response_format
|
24
|
+
@interceptors = interceptors
|
57
25
|
end
|
58
26
|
|
59
27
|
def call(env)
|
@@ -67,11 +35,22 @@ module ActionDispatch
|
|
67
35
|
|
68
36
|
response
|
69
37
|
rescue Exception => exception
|
38
|
+
invoke_interceptors(request, exception)
|
70
39
|
raise exception unless request.show_exceptions?
|
71
40
|
render_exception(request, exception)
|
72
41
|
end
|
73
42
|
|
74
43
|
private
|
44
|
+
def invoke_interceptors(request, exception)
|
45
|
+
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
|
46
|
+
wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
|
47
|
+
|
48
|
+
@interceptors.each do |interceptor|
|
49
|
+
interceptor.call(request, exception)
|
50
|
+
rescue Exception
|
51
|
+
log_error(request, wrapper)
|
52
|
+
end
|
53
|
+
end
|
75
54
|
|
76
55
|
def render_exception(request, exception)
|
77
56
|
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
|
@@ -79,7 +58,11 @@ module ActionDispatch
|
|
79
58
|
log_error(request, wrapper)
|
80
59
|
|
81
60
|
if request.get_header("action_dispatch.show_detailed_exceptions")
|
82
|
-
|
61
|
+
begin
|
62
|
+
content_type = request.formats.first
|
63
|
+
rescue ActionDispatch::Http::MimeNegotiation::InvalidType
|
64
|
+
content_type = Mime[:text]
|
65
|
+
end
|
83
66
|
|
84
67
|
if api_request?(content_type)
|
85
68
|
render_for_api_request(content_type, wrapper)
|
@@ -130,23 +113,13 @@ module ActionDispatch
|
|
130
113
|
end
|
131
114
|
|
132
115
|
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],
|
116
|
+
DebugView.new(
|
145
117
|
request: request,
|
118
|
+
exception_wrapper: wrapper,
|
146
119
|
exception: wrapper.exception,
|
147
|
-
traces: traces,
|
148
|
-
show_source_idx: source_to_show_id,
|
149
|
-
trace_to_show: trace_to_show,
|
120
|
+
traces: wrapper.traces,
|
121
|
+
show_source_idx: wrapper.source_to_show_id,
|
122
|
+
trace_to_show: wrapper.trace_to_show,
|
150
123
|
routes_inspector: routes_inspector(wrapper.exception),
|
151
124
|
source_extracts: wrapper.source_extracts,
|
152
125
|
line_number: wrapper.line_number,
|
@@ -160,27 +133,31 @@ module ActionDispatch
|
|
160
133
|
|
161
134
|
def log_error(request, wrapper)
|
162
135
|
logger = logger(request)
|
136
|
+
|
163
137
|
return unless logger
|
164
138
|
|
165
139
|
exception = wrapper.exception
|
140
|
+
trace = wrapper.exception_trace
|
166
141
|
|
167
|
-
|
168
|
-
|
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)
|
169
148
|
|
170
|
-
|
171
|
-
logger.fatal " "
|
172
|
-
logger.fatal "#{exception.class} (#{exception.message}):"
|
173
|
-
log_array logger, exception.annoted_source_code if exception.respond_to?(:annoted_source_code)
|
174
|
-
logger.fatal " "
|
175
|
-
log_array logger, trace
|
176
|
-
end
|
149
|
+
log_array(logger, message)
|
177
150
|
end
|
178
151
|
|
179
152
|
def log_array(logger, array)
|
153
|
+
lines = Array(array)
|
154
|
+
|
155
|
+
return if lines.empty?
|
156
|
+
|
180
157
|
if logger.formatter && logger.formatter.respond_to?(:tags_text)
|
181
|
-
logger.fatal
|
158
|
+
logger.fatal lines.join("\n#{logger.formatter.tags_text}")
|
182
159
|
else
|
183
|
-
logger.fatal
|
160
|
+
logger.fatal lines.join("\n")
|
184
161
|
end
|
185
162
|
end
|
186
163
|
|
@@ -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,66 @@
|
|
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
|
+
paths = [RESCUES_TEMPLATE_PATH]
|
14
|
+
lookup_context = ActionView::LookupContext.new(paths)
|
15
|
+
super(lookup_context, assigns, nil)
|
16
|
+
end
|
17
|
+
|
18
|
+
def compiled_method_container
|
19
|
+
self.class
|
20
|
+
end
|
21
|
+
|
22
|
+
def debug_params(params)
|
23
|
+
clean_params = params.clone
|
24
|
+
clean_params.delete("action")
|
25
|
+
clean_params.delete("controller")
|
26
|
+
|
27
|
+
if clean_params.empty?
|
28
|
+
"None"
|
29
|
+
else
|
30
|
+
PP.pp(clean_params, +"", 200)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def debug_headers(headers)
|
35
|
+
if headers.present?
|
36
|
+
headers.inspect.gsub(",", ",\n")
|
37
|
+
else
|
38
|
+
"None"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def debug_hash(object)
|
43
|
+
object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
|
44
|
+
end
|
45
|
+
|
46
|
+
def render(*)
|
47
|
+
logger = ActionView::Base.logger
|
48
|
+
|
49
|
+
if logger && logger.respond_to?(:silence)
|
50
|
+
logger.silence { super }
|
51
|
+
else
|
52
|
+
super
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def protect_against_forgery?
|
57
|
+
false
|
58
|
+
end
|
59
|
+
|
60
|
+
def params_valid?
|
61
|
+
@request.parameters
|
62
|
+
rescue ActionController::BadRequest
|
63
|
+
false
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -6,44 +6,72 @@ require "rack/utils"
|
|
6
6
|
module ActionDispatch
|
7
7
|
class ExceptionWrapper
|
8
8
|
cattr_accessor :rescue_responses, default: Hash.new(:internal_server_error).merge!(
|
9
|
-
"ActionController::RoutingError"
|
10
|
-
"AbstractController::ActionNotFound"
|
11
|
-
"ActionController::MethodNotAllowed"
|
12
|
-
"ActionController::UnknownHttpMethod"
|
13
|
-
"ActionController::NotImplemented"
|
14
|
-
"ActionController::UnknownFormat"
|
15
|
-
"
|
16
|
-
"ActionController::
|
17
|
-
"
|
18
|
-
"ActionController::
|
19
|
-
"
|
20
|
-
"
|
21
|
-
"
|
9
|
+
"ActionController::RoutingError" => :not_found,
|
10
|
+
"AbstractController::ActionNotFound" => :not_found,
|
11
|
+
"ActionController::MethodNotAllowed" => :method_not_allowed,
|
12
|
+
"ActionController::UnknownHttpMethod" => :method_not_allowed,
|
13
|
+
"ActionController::NotImplemented" => :not_implemented,
|
14
|
+
"ActionController::UnknownFormat" => :not_acceptable,
|
15
|
+
"ActionDispatch::Http::MimeNegotiation::InvalidType" => :not_acceptable,
|
16
|
+
"ActionController::MissingExactTemplate" => :not_acceptable,
|
17
|
+
"ActionController::InvalidAuthenticityToken" => :unprocessable_entity,
|
18
|
+
"ActionController::InvalidCrossOriginRequest" => :unprocessable_entity,
|
19
|
+
"ActionDispatch::Http::Parameters::ParseError" => :bad_request,
|
20
|
+
"ActionController::BadRequest" => :bad_request,
|
21
|
+
"ActionController::ParameterMissing" => :bad_request,
|
22
|
+
"Rack::QueryParser::ParameterTypeError" => :bad_request,
|
23
|
+
"Rack::QueryParser::InvalidParameterError" => :bad_request
|
22
24
|
)
|
23
25
|
|
24
26
|
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"
|
27
|
+
"ActionView::MissingTemplate" => "missing_template",
|
28
|
+
"ActionController::RoutingError" => "routing_error",
|
29
|
+
"AbstractController::ActionNotFound" => "unknown_action",
|
30
|
+
"ActiveRecord::StatementInvalid" => "invalid_statement",
|
31
|
+
"ActionView::Template::Error" => "template_error",
|
32
|
+
"ActionController::MissingExactTemplate" => "missing_exact_template",
|
30
33
|
)
|
31
34
|
|
32
|
-
|
35
|
+
cattr_accessor :wrapper_exceptions, default: [
|
36
|
+
"ActionView::Template::Error"
|
37
|
+
]
|
38
|
+
|
39
|
+
cattr_accessor :silent_exceptions, default: [
|
40
|
+
"ActionController::RoutingError",
|
41
|
+
"ActionDispatch::Http::MimeNegotiation::InvalidType"
|
42
|
+
]
|
43
|
+
|
44
|
+
attr_reader :backtrace_cleaner, :exception, :wrapped_causes, :line_number, :file
|
33
45
|
|
34
46
|
def initialize(backtrace_cleaner, exception)
|
35
47
|
@backtrace_cleaner = backtrace_cleaner
|
36
|
-
@exception =
|
48
|
+
@exception = exception
|
49
|
+
@exception_class_name = @exception.class.name
|
50
|
+
@wrapped_causes = wrapped_causes_for(exception, backtrace_cleaner)
|
37
51
|
|
38
52
|
expand_backtrace if exception.is_a?(SyntaxError) || exception.cause.is_a?(SyntaxError)
|
39
53
|
end
|
40
54
|
|
55
|
+
def unwrapped_exception
|
56
|
+
if wrapper_exceptions.include?(@exception_class_name)
|
57
|
+
exception.cause
|
58
|
+
else
|
59
|
+
exception
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
41
63
|
def rescue_template
|
42
|
-
@@rescue_templates[@
|
64
|
+
@@rescue_templates[@exception_class_name]
|
43
65
|
end
|
44
66
|
|
45
67
|
def status_code
|
46
|
-
self.class.status_code_for_exception(
|
68
|
+
self.class.status_code_for_exception(unwrapped_exception.class.name)
|
69
|
+
end
|
70
|
+
|
71
|
+
def exception_trace
|
72
|
+
trace = application_trace
|
73
|
+
trace = framework_trace if trace.empty? && !silent_exceptions.include?(@exception_class_name)
|
74
|
+
trace
|
47
75
|
end
|
48
76
|
|
49
77
|
def application_trace
|
@@ -64,7 +92,11 @@ module ActionDispatch
|
|
64
92
|
full_trace_with_ids = []
|
65
93
|
|
66
94
|
full_trace.each_with_index do |trace, idx|
|
67
|
-
trace_with_id = {
|
95
|
+
trace_with_id = {
|
96
|
+
exception_object_id: @exception.object_id,
|
97
|
+
id: idx,
|
98
|
+
trace: trace
|
99
|
+
}
|
68
100
|
|
69
101
|
if application_trace.include?(trace)
|
70
102
|
application_trace_with_ids << trace_with_id
|
@@ -97,18 +129,31 @@ module ActionDispatch
|
|
97
129
|
end
|
98
130
|
end
|
99
131
|
|
100
|
-
|
132
|
+
def trace_to_show
|
133
|
+
if traces["Application Trace"].empty? && rescue_template != "routing_error"
|
134
|
+
"Full Trace"
|
135
|
+
else
|
136
|
+
"Application Trace"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def source_to_show_id
|
141
|
+
(traces[trace_to_show].first || {})[:id]
|
142
|
+
end
|
101
143
|
|
144
|
+
private
|
102
145
|
def backtrace
|
103
146
|
Array(@exception.backtrace)
|
104
147
|
end
|
105
148
|
|
106
|
-
def
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
149
|
+
def causes_for(exception)
|
150
|
+
return enum_for(__method__, exception) unless block_given?
|
151
|
+
|
152
|
+
yield exception while exception = exception.cause
|
153
|
+
end
|
154
|
+
|
155
|
+
def wrapped_causes_for(exception, backtrace_cleaner)
|
156
|
+
causes_for(exception).map { |cause| self.class.new(backtrace_cleaner, cause) }
|
112
157
|
end
|
113
158
|
|
114
159
|
def 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,121 @@
|
|
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 explicitly permitting
|
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/ } }
|
13
|
+
#
|
14
|
+
# When a request comes to an unauthorized host, the +response_app+
|
15
|
+
# application will be executed and rendered. If no +response_app+ is given, a
|
16
|
+
# default one will run, which responds with +403 Forbidden+.
|
17
|
+
class HostAuthorization
|
18
|
+
class Permissions # :nodoc:
|
19
|
+
def initialize(hosts)
|
20
|
+
@hosts = sanitize_hosts(hosts)
|
21
|
+
end
|
22
|
+
|
23
|
+
def empty?
|
24
|
+
@hosts.empty?
|
25
|
+
end
|
26
|
+
|
27
|
+
def allows?(host)
|
28
|
+
@hosts.any? do |allowed|
|
29
|
+
allowed === host
|
30
|
+
rescue
|
31
|
+
# IPAddr#=== raises an error if you give it a hostname instead of
|
32
|
+
# IP. Treat similar errors as blocked access.
|
33
|
+
false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
def sanitize_hosts(hosts)
|
39
|
+
Array(hosts).map do |host|
|
40
|
+
case host
|
41
|
+
when Regexp then sanitize_regexp(host)
|
42
|
+
when String then sanitize_string(host)
|
43
|
+
else host
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def sanitize_regexp(host)
|
49
|
+
/\A#{host}\z/
|
50
|
+
end
|
51
|
+
|
52
|
+
def sanitize_string(host)
|
53
|
+
if host.start_with?(".")
|
54
|
+
/\A(.+\.)?#{Regexp.escape(host[1..-1])}\z/i
|
55
|
+
else
|
56
|
+
/\A#{host}\z/i
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
DEFAULT_RESPONSE_APP = -> env do
|
62
|
+
request = Request.new(env)
|
63
|
+
|
64
|
+
format = request.xhr? ? "text/plain" : "text/html"
|
65
|
+
template = DebugView.new(host: request.host)
|
66
|
+
body = template.render(template: "rescues/blocked_host", layout: "rescues/layout")
|
67
|
+
|
68
|
+
[403, {
|
69
|
+
"Content-Type" => "#{format}; charset=#{Response.default_charset}",
|
70
|
+
"Content-Length" => body.bytesize.to_s,
|
71
|
+
}, [body]]
|
72
|
+
end
|
73
|
+
|
74
|
+
def initialize(app, hosts, deprecated_response_app = nil, exclude: nil, response_app: nil)
|
75
|
+
@app = app
|
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
|
+
|
88
|
+
@response_app = response_app || DEFAULT_RESPONSE_APP
|
89
|
+
end
|
90
|
+
|
91
|
+
def call(env)
|
92
|
+
return @app.call(env) if @permissions.empty?
|
93
|
+
|
94
|
+
request = Request.new(env)
|
95
|
+
|
96
|
+
if authorized?(request) || excluded?(request)
|
97
|
+
mark_as_authorized(request)
|
98
|
+
@app.call(env)
|
99
|
+
else
|
100
|
+
@response_app.call(env)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
def authorized?(request)
|
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
|
112
|
+
|
113
|
+
def excluded?(request)
|
114
|
+
@exclude && @exclude.call(request)
|
115
|
+
end
|
116
|
+
|
117
|
+
def mark_as_authorized(request)
|
118
|
+
request.set_header("action_dispatch.authorized_host", request.host)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|