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
@@ -3,57 +3,28 @@
|
|
3
3
|
require "action_dispatch/http/request"
|
4
4
|
require "action_dispatch/middleware/exception_wrapper"
|
5
5
|
require "action_dispatch/routing/inspector"
|
6
|
+
|
7
|
+
require "active_support/actionable_error"
|
8
|
+
|
6
9
|
require "action_view"
|
7
10
|
require "action_view/base"
|
8
11
|
|
9
|
-
require "pp"
|
10
|
-
|
11
12
|
module ActionDispatch
|
12
13
|
# This middleware is responsible for logging exceptions and
|
13
14
|
# showing a debugging page in case the request is local.
|
14
15
|
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
|
16
|
+
cattr_reader :interceptors, instance_accessor: false, default: []
|
44
17
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
super
|
49
|
-
end
|
50
|
-
end
|
18
|
+
def self.register_interceptor(object = nil, &block)
|
19
|
+
interceptor = object || block
|
20
|
+
interceptors << interceptor
|
51
21
|
end
|
52
22
|
|
53
|
-
def initialize(app, routes_app = nil, response_format = :default)
|
23
|
+
def initialize(app, routes_app = nil, response_format = :default, interceptors = self.class.interceptors)
|
54
24
|
@app = app
|
55
25
|
@routes_app = routes_app
|
56
26
|
@response_format = response_format
|
27
|
+
@interceptors = interceptors
|
57
28
|
end
|
58
29
|
|
59
30
|
def call(env)
|
@@ -67,19 +38,35 @@ module ActionDispatch
|
|
67
38
|
|
68
39
|
response
|
69
40
|
rescue Exception => exception
|
41
|
+
invoke_interceptors(request, exception)
|
70
42
|
raise exception unless request.show_exceptions?
|
71
43
|
render_exception(request, exception)
|
72
44
|
end
|
73
45
|
|
74
46
|
private
|
75
47
|
|
48
|
+
def invoke_interceptors(request, exception)
|
49
|
+
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
|
50
|
+
wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
|
51
|
+
|
52
|
+
@interceptors.each do |interceptor|
|
53
|
+
interceptor.call(request, exception)
|
54
|
+
rescue Exception
|
55
|
+
log_error(request, wrapper)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
76
59
|
def render_exception(request, exception)
|
77
60
|
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
|
78
61
|
wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
|
79
62
|
log_error(request, wrapper)
|
80
63
|
|
81
64
|
if request.get_header("action_dispatch.show_detailed_exceptions")
|
82
|
-
|
65
|
+
begin
|
66
|
+
content_type = request.formats.first
|
67
|
+
rescue Mime::Type::InvalidMimeType
|
68
|
+
render_for_api_request(Mime[:text], wrapper)
|
69
|
+
end
|
83
70
|
|
84
71
|
if api_request?(content_type)
|
85
72
|
render_for_api_request(content_type, wrapper)
|
@@ -130,23 +117,13 @@ module ActionDispatch
|
|
130
117
|
end
|
131
118
|
|
132
119
|
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],
|
120
|
+
DebugView.new(
|
145
121
|
request: request,
|
122
|
+
exception_wrapper: wrapper,
|
146
123
|
exception: wrapper.exception,
|
147
|
-
traces: traces,
|
148
|
-
show_source_idx: source_to_show_id,
|
149
|
-
trace_to_show: trace_to_show,
|
124
|
+
traces: wrapper.traces,
|
125
|
+
show_source_idx: wrapper.source_to_show_id,
|
126
|
+
trace_to_show: wrapper.trace_to_show,
|
150
127
|
routes_inspector: routes_inspector(wrapper.exception),
|
151
128
|
source_extracts: wrapper.source_extracts,
|
152
129
|
line_number: wrapper.line_number,
|
@@ -168,11 +145,14 @@ module ActionDispatch
|
|
168
145
|
trace = wrapper.framework_trace if trace.empty?
|
169
146
|
|
170
147
|
ActiveSupport::Deprecation.silence do
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
148
|
+
message = []
|
149
|
+
message << " "
|
150
|
+
message << "#{exception.class} (#{exception.message}):"
|
151
|
+
message.concat(exception.annotated_source_code) if exception.respond_to?(:annotated_source_code)
|
152
|
+
message << " "
|
153
|
+
message.concat(trace)
|
154
|
+
|
155
|
+
log_array(logger, message)
|
176
156
|
end
|
177
157
|
end
|
178
158
|
|
@@ -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,68 @@
|
|
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)
|
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
|
+
begin
|
62
|
+
@request.parameters
|
63
|
+
rescue ActionController::BadRequest
|
64
|
+
false
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -12,6 +12,8 @@ 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,
|
16
|
+
"ActionController::MissingExactTemplate" => :not_acceptable,
|
15
17
|
"ActionController::InvalidAuthenticityToken" => :unprocessable_entity,
|
16
18
|
"ActionController::InvalidCrossOriginRequest" => :unprocessable_entity,
|
17
19
|
"ActionDispatch::Http::Parameters::ParseError" => :bad_request,
|
@@ -22,28 +24,42 @@ module ActionDispatch
|
|
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
|
+
attr_reader :backtrace_cleaner, :exception, :wrapped_causes, :line_number, :file
|
33
40
|
|
34
41
|
def initialize(backtrace_cleaner, exception)
|
35
42
|
@backtrace_cleaner = backtrace_cleaner
|
36
|
-
@exception =
|
43
|
+
@exception = exception
|
44
|
+
@wrapped_causes = wrapped_causes_for(exception, backtrace_cleaner)
|
37
45
|
|
38
46
|
expand_backtrace if exception.is_a?(SyntaxError) || exception.cause.is_a?(SyntaxError)
|
39
47
|
end
|
40
48
|
|
49
|
+
def unwrapped_exception
|
50
|
+
if wrapper_exceptions.include?(exception.class.to_s)
|
51
|
+
exception.cause
|
52
|
+
else
|
53
|
+
exception
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
41
57
|
def rescue_template
|
42
58
|
@@rescue_templates[@exception.class.name]
|
43
59
|
end
|
44
60
|
|
45
61
|
def status_code
|
46
|
-
self.class.status_code_for_exception(
|
62
|
+
self.class.status_code_for_exception(unwrapped_exception.class.name)
|
47
63
|
end
|
48
64
|
|
49
65
|
def application_trace
|
@@ -64,7 +80,11 @@ module ActionDispatch
|
|
64
80
|
full_trace_with_ids = []
|
65
81
|
|
66
82
|
full_trace.each_with_index do |trace, idx|
|
67
|
-
trace_with_id = {
|
83
|
+
trace_with_id = {
|
84
|
+
exception_object_id: @exception.object_id,
|
85
|
+
id: idx,
|
86
|
+
trace: trace
|
87
|
+
}
|
68
88
|
|
69
89
|
if application_trace.include?(trace)
|
70
90
|
application_trace_with_ids << trace_with_id
|
@@ -97,18 +117,32 @@ module ActionDispatch
|
|
97
117
|
end
|
98
118
|
end
|
99
119
|
|
120
|
+
def trace_to_show
|
121
|
+
if traces["Application Trace"].empty? && rescue_template != "routing_error"
|
122
|
+
"Full Trace"
|
123
|
+
else
|
124
|
+
"Application Trace"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def source_to_show_id
|
129
|
+
(traces[trace_to_show].first || {})[:id]
|
130
|
+
end
|
131
|
+
|
100
132
|
private
|
101
133
|
|
102
134
|
def backtrace
|
103
135
|
Array(@exception.backtrace)
|
104
136
|
end
|
105
137
|
|
106
|
-
def
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
138
|
+
def causes_for(exception)
|
139
|
+
return enum_for(__method__, exception) unless block_given?
|
140
|
+
|
141
|
+
yield exception while exception = exception.cause
|
142
|
+
end
|
143
|
+
|
144
|
+
def wrapped_causes_for(exception, backtrace_cleaner)
|
145
|
+
causes_for(exception).map { |cause| self.class.new(backtrace_cleaner, cause) }
|
112
146
|
end
|
113
147
|
|
114
148
|
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,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 explicitly permitting
|
7
|
+
# the 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
|