actionpack 7.0.8 → 7.1.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +360 -353
- data/MIT-LICENSE +1 -1
- data/README.rdoc +2 -2
- data/lib/abstract_controller/base.rb +20 -11
- data/lib/abstract_controller/caching/fragments.rb +2 -0
- data/lib/abstract_controller/callbacks.rb +31 -6
- data/lib/abstract_controller/deprecator.rb +7 -0
- data/lib/abstract_controller/helpers.rb +61 -18
- data/lib/abstract_controller/railties/routes_helpers.rb +1 -16
- data/lib/abstract_controller/rendering.rb +3 -3
- data/lib/abstract_controller/translation.rb +7 -4
- data/lib/abstract_controller/url_for.rb +2 -0
- data/lib/abstract_controller.rb +6 -0
- data/lib/action_controller/api.rb +5 -3
- data/lib/action_controller/base.rb +3 -17
- data/lib/action_controller/caching.rb +2 -0
- data/lib/action_controller/deprecator.rb +7 -0
- data/lib/action_controller/form_builder.rb +2 -0
- data/lib/action_controller/log_subscriber.rb +16 -4
- data/lib/action_controller/metal/content_security_policy.rb +1 -1
- data/lib/action_controller/metal/data_streaming.rb +2 -0
- data/lib/action_controller/metal/default_headers.rb +2 -0
- data/lib/action_controller/metal/etag_with_flash.rb +2 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +2 -0
- data/lib/action_controller/metal/exceptions.rb +8 -0
- data/lib/action_controller/metal/head.rb +8 -6
- data/lib/action_controller/metal/helpers.rb +3 -14
- data/lib/action_controller/metal/http_authentication.rb +17 -8
- data/lib/action_controller/metal/implicit_render.rb +5 -3
- data/lib/action_controller/metal/instrumentation.rb +8 -1
- data/lib/action_controller/metal/live.rb +24 -0
- data/lib/action_controller/metal/mime_responds.rb +2 -2
- data/lib/action_controller/metal/params_wrapper.rb +4 -2
- data/lib/action_controller/metal/permissions_policy.rb +1 -1
- data/lib/action_controller/metal/redirecting.rb +7 -7
- data/lib/action_controller/metal/renderers.rb +2 -2
- data/lib/action_controller/metal/rendering.rb +0 -7
- data/lib/action_controller/metal/request_forgery_protection.rb +139 -50
- data/lib/action_controller/metal/rescue.rb +2 -0
- data/lib/action_controller/metal/streaming.rb +70 -30
- data/lib/action_controller/metal/strong_parameters.rb +132 -52
- data/lib/action_controller/metal/url_for.rb +7 -0
- data/lib/action_controller/metal.rb +79 -21
- data/lib/action_controller/railtie.rb +22 -9
- data/lib/action_controller/renderer.rb +98 -65
- data/lib/action_controller/test_case.rb +15 -5
- data/lib/action_controller.rb +8 -1
- data/lib/action_dispatch/constants.rb +32 -0
- data/lib/action_dispatch/deprecator.rb +7 -0
- data/lib/action_dispatch/http/cache.rb +1 -3
- data/lib/action_dispatch/http/content_security_policy.rb +9 -8
- data/lib/action_dispatch/http/filter_parameters.rb +11 -5
- data/lib/action_dispatch/http/headers.rb +2 -0
- data/lib/action_dispatch/http/mime_negotiation.rb +22 -22
- data/lib/action_dispatch/http/mime_type.rb +35 -12
- data/lib/action_dispatch/http/mime_types.rb +3 -1
- data/lib/action_dispatch/http/parameters.rb +1 -1
- data/lib/action_dispatch/http/permissions_policy.rb +40 -18
- data/lib/action_dispatch/http/rack_cache.rb +2 -0
- data/lib/action_dispatch/http/request.rb +48 -14
- data/lib/action_dispatch/http/response.rb +80 -59
- data/lib/action_dispatch/http/upload.rb +2 -0
- data/lib/action_dispatch/journey/formatter.rb +8 -2
- data/lib/action_dispatch/journey/path/pattern.rb +14 -14
- data/lib/action_dispatch/journey/route.rb +3 -2
- data/lib/action_dispatch/journey/router.rb +9 -8
- data/lib/action_dispatch/journey/routes.rb +2 -2
- data/lib/action_dispatch/log_subscriber.rb +23 -0
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +5 -6
- data/lib/action_dispatch/middleware/assume_ssl.rb +24 -0
- data/lib/action_dispatch/middleware/callbacks.rb +2 -0
- data/lib/action_dispatch/middleware/cookies.rb +81 -98
- data/lib/action_dispatch/middleware/debug_exceptions.rb +26 -25
- data/lib/action_dispatch/middleware/debug_locks.rb +4 -1
- data/lib/action_dispatch/middleware/debug_view.rb +7 -2
- data/lib/action_dispatch/middleware/exception_wrapper.rb +186 -27
- data/lib/action_dispatch/middleware/executor.rb +1 -1
- data/lib/action_dispatch/middleware/flash.rb +7 -0
- data/lib/action_dispatch/middleware/host_authorization.rb +6 -3
- data/lib/action_dispatch/middleware/public_exceptions.rb +5 -3
- data/lib/action_dispatch/middleware/reloader.rb +7 -5
- data/lib/action_dispatch/middleware/remote_ip.rb +17 -16
- data/lib/action_dispatch/middleware/request_id.rb +2 -0
- data/lib/action_dispatch/middleware/server_timing.rb +4 -4
- data/lib/action_dispatch/middleware/session/abstract_store.rb +5 -0
- data/lib/action_dispatch/middleware/session/cache_store.rb +2 -0
- data/lib/action_dispatch/middleware/session/cookie_store.rb +11 -5
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +3 -1
- data/lib/action_dispatch/middleware/show_exceptions.rb +19 -15
- data/lib/action_dispatch/middleware/ssl.rb +18 -6
- data/lib/action_dispatch/middleware/stack.rb +7 -2
- data/lib/action_dispatch/middleware/static.rb +12 -8
- data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +7 -7
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +17 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +16 -12
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +3 -0
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +46 -37
- data/lib/action_dispatch/railtie.rb +14 -4
- data/lib/action_dispatch/request/session.rb +16 -6
- data/lib/action_dispatch/request/utils.rb +8 -3
- data/lib/action_dispatch/routing/inspector.rb +54 -6
- data/lib/action_dispatch/routing/mapper.rb +35 -24
- data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -0
- data/lib/action_dispatch/routing/redirection.rb +15 -6
- data/lib/action_dispatch/routing/route_set.rb +52 -22
- data/lib/action_dispatch/routing/routes_proxy.rb +10 -15
- data/lib/action_dispatch/routing/url_for.rb +5 -1
- data/lib/action_dispatch/routing.rb +7 -7
- data/lib/action_dispatch/system_test_case.rb +3 -3
- data/lib/action_dispatch/system_testing/browser.rb +20 -19
- data/lib/action_dispatch/system_testing/driver.rb +13 -21
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +27 -16
- data/lib/action_dispatch/testing/assertion_response.rb +1 -1
- data/lib/action_dispatch/testing/assertions/response.rb +13 -6
- data/lib/action_dispatch/testing/assertions/routing.rb +67 -28
- data/lib/action_dispatch/testing/assertions.rb +3 -1
- data/lib/action_dispatch/testing/integration.rb +27 -17
- data/lib/action_dispatch/testing/request_encoder.rb +4 -1
- data/lib/action_dispatch/testing/test_process.rb +4 -3
- data/lib/action_dispatch/testing/test_request.rb +1 -1
- data/lib/action_dispatch/testing/test_response.rb +23 -9
- data/lib/action_dispatch.rb +37 -4
- data/lib/action_pack/gem_version.rb +4 -4
- data/lib/action_pack/version.rb +1 -1
- data/lib/action_pack.rb +1 -1
- metadata +64 -28
@@ -6,6 +6,8 @@ require "action_dispatch/routing/inspector"
|
|
6
6
|
require "action_view"
|
7
7
|
|
8
8
|
module ActionDispatch
|
9
|
+
# = Action Dispatch \DebugExceptions
|
10
|
+
#
|
9
11
|
# This middleware is responsible for logging exceptions and
|
10
12
|
# showing a debugging page in case the request is local.
|
11
13
|
class DebugExceptions
|
@@ -24,26 +26,26 @@ module ActionDispatch
|
|
24
26
|
end
|
25
27
|
|
26
28
|
def call(env)
|
27
|
-
request = ActionDispatch::Request.new env
|
28
29
|
_, headers, body = response = @app.call(env)
|
29
30
|
|
30
|
-
if headers[
|
31
|
+
if headers[Constants::X_CASCADE] == "pass"
|
31
32
|
body.close if body.respond_to?(:close)
|
32
33
|
raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
|
33
34
|
end
|
34
35
|
|
35
36
|
response
|
36
37
|
rescue Exception => exception
|
37
|
-
|
38
|
-
|
39
|
-
|
38
|
+
request = ActionDispatch::Request.new env
|
39
|
+
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
|
40
|
+
wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
|
41
|
+
|
42
|
+
invoke_interceptors(request, exception, wrapper)
|
43
|
+
raise exception unless wrapper.show?(request)
|
44
|
+
render_exception(request, exception, wrapper)
|
40
45
|
end
|
41
46
|
|
42
47
|
private
|
43
|
-
def invoke_interceptors(request, exception)
|
44
|
-
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
|
45
|
-
wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
|
46
|
-
|
48
|
+
def invoke_interceptors(request, exception, wrapper)
|
47
49
|
@interceptors.each do |interceptor|
|
48
50
|
interceptor.call(request, exception)
|
49
51
|
rescue Exception
|
@@ -51,9 +53,7 @@ module ActionDispatch
|
|
51
53
|
end
|
52
54
|
end
|
53
55
|
|
54
|
-
def render_exception(request, exception)
|
55
|
-
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
|
56
|
-
wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
|
56
|
+
def render_exception(request, exception, wrapper)
|
57
57
|
log_error(request, wrapper)
|
58
58
|
|
59
59
|
if request.get_header("action_dispatch.show_detailed_exceptions")
|
@@ -94,7 +94,7 @@ module ActionDispatch
|
|
94
94
|
wrapper.status_code,
|
95
95
|
Rack::Utils::HTTP_STATUS_CODES[500]
|
96
96
|
),
|
97
|
-
exception: wrapper.
|
97
|
+
exception: wrapper.exception_inspect,
|
98
98
|
traces: wrapper.traces
|
99
99
|
}
|
100
100
|
|
@@ -115,19 +115,19 @@ module ActionDispatch
|
|
115
115
|
DebugView.new(
|
116
116
|
request: request,
|
117
117
|
exception_wrapper: wrapper,
|
118
|
+
# Everything should use the wrapper, but we need to pass
|
119
|
+
# `exception` for legacy code.
|
118
120
|
exception: wrapper.exception,
|
119
121
|
traces: wrapper.traces,
|
120
122
|
show_source_idx: wrapper.source_to_show_id,
|
121
123
|
trace_to_show: wrapper.trace_to_show,
|
122
|
-
routes_inspector: routes_inspector(wrapper
|
124
|
+
routes_inspector: routes_inspector(wrapper),
|
123
125
|
source_extracts: wrapper.source_extracts,
|
124
|
-
line_number: wrapper.line_number,
|
125
|
-
file: wrapper.file
|
126
126
|
)
|
127
127
|
end
|
128
128
|
|
129
129
|
def render(status, body, format)
|
130
|
-
[status, {
|
130
|
+
[status, { Rack::CONTENT_TYPE => "#{format}; charset=#{Response.default_charset}", Rack::CONTENT_LENGTH => body.bytesize.to_s }, [body]]
|
131
131
|
end
|
132
132
|
|
133
133
|
def log_error(request, wrapper)
|
@@ -136,26 +136,27 @@ module ActionDispatch
|
|
136
136
|
return unless logger
|
137
137
|
return if !log_rescued_responses?(request) && wrapper.rescue_response?
|
138
138
|
|
139
|
-
exception = wrapper.exception
|
140
139
|
trace = wrapper.exception_trace
|
141
140
|
|
142
141
|
message = []
|
143
142
|
message << " "
|
144
|
-
message << "#{
|
145
|
-
message.concat(
|
143
|
+
message << "#{wrapper.exception_class_name} (#{wrapper.message}):"
|
144
|
+
message.concat(wrapper.annotated_source_code)
|
146
145
|
message << " "
|
147
146
|
message.concat(trace)
|
148
147
|
|
149
|
-
log_array(logger, message)
|
148
|
+
log_array(logger, message, request)
|
150
149
|
end
|
151
150
|
|
152
|
-
def log_array(logger, lines)
|
151
|
+
def log_array(logger, lines, request)
|
153
152
|
return if lines.empty?
|
154
153
|
|
154
|
+
level = request.get_header("action_dispatch.debug_exception_log_level")
|
155
|
+
|
155
156
|
if logger.formatter && logger.formatter.respond_to?(:tags_text)
|
156
|
-
logger.
|
157
|
+
logger.add(level, lines.join("\n#{logger.formatter.tags_text}"))
|
157
158
|
else
|
158
|
-
logger.
|
159
|
+
logger.add(level, lines.join("\n"))
|
159
160
|
end
|
160
161
|
end
|
161
162
|
|
@@ -168,7 +169,7 @@ module ActionDispatch
|
|
168
169
|
end
|
169
170
|
|
170
171
|
def routes_inspector(exception)
|
171
|
-
if @routes_app.respond_to?(:routes) && (exception.
|
172
|
+
if @routes_app.respond_to?(:routes) && (exception.routing_error? || exception.template_error?)
|
172
173
|
ActionDispatch::Routing::RoutesInspector.new(@routes_app.routes.routes)
|
173
174
|
end
|
174
175
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActionDispatch
|
4
|
+
# = Action Dispatch \DebugLocks
|
5
|
+
#
|
4
6
|
# This middleware can be used to diagnose deadlocks in the autoload interlock.
|
5
7
|
#
|
6
8
|
# To use it, insert it near the top of the middleware stack, using
|
@@ -97,7 +99,8 @@ module ActionDispatch
|
|
97
99
|
msg << "\n#{info[:backtrace].join("\n")}\n" if info[:backtrace]
|
98
100
|
end.join("\n\n---\n\n\n")
|
99
101
|
|
100
|
-
[200, {
|
102
|
+
[200, { Rack::CONTENT_TYPE => "text/plain; charset=#{ActionDispatch::Response.default_charset}",
|
103
|
+
Rack::CONTENT_LENGTH => str.size.to_s }, [str]]
|
101
104
|
end
|
102
105
|
|
103
106
|
def blocked_by?(victim, blocker, all_threads)
|
@@ -7,18 +7,23 @@ require "action_view/base"
|
|
7
7
|
|
8
8
|
module ActionDispatch
|
9
9
|
class DebugView < ActionView::Base # :nodoc:
|
10
|
-
|
10
|
+
RESCUES_TEMPLATE_PATHS = [File.expand_path("templates", __dir__)]
|
11
11
|
|
12
12
|
def initialize(assigns)
|
13
|
-
paths =
|
13
|
+
paths = RESCUES_TEMPLATE_PATHS.dup
|
14
14
|
lookup_context = ActionView::LookupContext.new(paths)
|
15
15
|
super(lookup_context, assigns, nil)
|
16
|
+
@exception_wrapper = assigns[:exception_wrapper]
|
16
17
|
end
|
17
18
|
|
18
19
|
def compiled_method_container
|
19
20
|
self.class
|
20
21
|
end
|
21
22
|
|
23
|
+
def error_highlight_available?
|
24
|
+
@exception_wrapper.error_highlight_available?
|
25
|
+
end
|
26
|
+
|
22
27
|
def debug_params(params)
|
23
28
|
clean_params = params.clone
|
24
29
|
clean_params.delete("action")
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_support/core_ext/module/attribute_accessors"
|
4
|
+
require "active_support/syntax_error_proxy"
|
5
|
+
require "active_support/core_ext/thread/backtrace/location"
|
4
6
|
require "rack/utils"
|
5
7
|
|
6
8
|
module ActionDispatch
|
@@ -41,22 +43,76 @@ module ActionDispatch
|
|
41
43
|
"ActionDispatch::Http::MimeNegotiation::InvalidType"
|
42
44
|
]
|
43
45
|
|
44
|
-
attr_reader :backtrace_cleaner, :
|
46
|
+
attr_reader :backtrace_cleaner, :wrapped_causes, :exception_class_name, :exception
|
45
47
|
|
46
48
|
def initialize(backtrace_cleaner, exception)
|
47
49
|
@backtrace_cleaner = backtrace_cleaner
|
48
|
-
@
|
49
|
-
@exception_class_name = @exception.class.name
|
50
|
+
@exception_class_name = exception.class.name
|
50
51
|
@wrapped_causes = wrapped_causes_for(exception, backtrace_cleaner)
|
52
|
+
@exception = exception
|
53
|
+
if exception.is_a?(SyntaxError)
|
54
|
+
@exception = ActiveSupport::SyntaxErrorProxy.new(exception)
|
55
|
+
end
|
56
|
+
@backtrace = build_backtrace
|
57
|
+
end
|
58
|
+
|
59
|
+
def routing_error?
|
60
|
+
@exception.is_a?(ActionController::RoutingError)
|
61
|
+
end
|
51
62
|
|
52
|
-
|
63
|
+
def template_error?
|
64
|
+
@exception.is_a?(ActionView::Template::Error)
|
65
|
+
end
|
66
|
+
|
67
|
+
def sub_template_message
|
68
|
+
@exception.sub_template_message
|
69
|
+
end
|
70
|
+
|
71
|
+
def has_cause?
|
72
|
+
@exception.cause
|
73
|
+
end
|
74
|
+
|
75
|
+
def failures
|
76
|
+
@exception.failures
|
77
|
+
end
|
78
|
+
|
79
|
+
def has_corrections?
|
80
|
+
@exception.respond_to?(:original_message) && @exception.respond_to?(:corrections)
|
81
|
+
end
|
82
|
+
|
83
|
+
def original_message
|
84
|
+
@exception.original_message
|
85
|
+
end
|
86
|
+
|
87
|
+
def corrections
|
88
|
+
@exception.corrections
|
89
|
+
end
|
90
|
+
|
91
|
+
def file_name
|
92
|
+
@exception.file_name
|
93
|
+
end
|
94
|
+
|
95
|
+
def line_number
|
96
|
+
@exception.line_number
|
97
|
+
end
|
98
|
+
|
99
|
+
def actions
|
100
|
+
ActiveSupport::ActionableError.actions(@exception)
|
53
101
|
end
|
54
102
|
|
55
103
|
def unwrapped_exception
|
56
104
|
if wrapper_exceptions.include?(@exception_class_name)
|
57
|
-
exception.cause
|
105
|
+
@exception.cause
|
106
|
+
else
|
107
|
+
@exception
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def annotated_source_code
|
112
|
+
if exception.respond_to?(:annotated_source_code)
|
113
|
+
exception.annotated_source_code
|
58
114
|
else
|
59
|
-
|
115
|
+
[]
|
60
116
|
end
|
61
117
|
end
|
62
118
|
|
@@ -118,21 +174,44 @@ module ActionDispatch
|
|
118
174
|
Rack::Utils.status_code(@@rescue_responses[class_name])
|
119
175
|
end
|
120
176
|
|
177
|
+
def show?(request)
|
178
|
+
# We're treating `nil` as "unset", and we want the default setting to be
|
179
|
+
# `:all`. This logic should be extracted to `env_config` and calculated
|
180
|
+
# once.
|
181
|
+
config = request.get_header("action_dispatch.show_exceptions")
|
182
|
+
|
183
|
+
# Include true and false for backwards compatibility.
|
184
|
+
case config
|
185
|
+
when :none
|
186
|
+
false
|
187
|
+
when :rescuable
|
188
|
+
rescue_response?
|
189
|
+
when true
|
190
|
+
ActionDispatch.deprecator.warn("Setting action_dispatch.show_exceptions to true is deprecated. Set to :all instead.")
|
191
|
+
true
|
192
|
+
when false
|
193
|
+
ActionDispatch.deprecator.warn("Setting action_dispatch.show_exceptions to false is deprecated. Set to :none instead.")
|
194
|
+
false
|
195
|
+
else
|
196
|
+
true
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
121
200
|
def rescue_response?
|
122
201
|
@@rescue_responses.key?(exception.class.name)
|
123
202
|
end
|
124
203
|
|
125
204
|
def source_extracts
|
126
205
|
backtrace.map do |trace|
|
127
|
-
|
128
|
-
|
129
|
-
{
|
130
|
-
code: source_fragment(file, line_number),
|
131
|
-
line_number: line_number
|
132
|
-
}
|
206
|
+
extract_source(trace)
|
133
207
|
end
|
134
208
|
end
|
135
209
|
|
210
|
+
def error_highlight_available?
|
211
|
+
# ErrorHighlight.spot with backtrace_location keyword is available since error_highlight 0.4.0
|
212
|
+
defined?(ErrorHighlight) && Gem::Version.new(ErrorHighlight::VERSION) >= Gem::Version.new("0.4.0")
|
213
|
+
end
|
214
|
+
|
136
215
|
def trace_to_show
|
137
216
|
if traces["Application Trace"].empty? && rescue_template != "routing_error"
|
138
217
|
"Full Trace"
|
@@ -145,9 +224,65 @@ module ActionDispatch
|
|
145
224
|
(traces[trace_to_show].first || {})[:id]
|
146
225
|
end
|
147
226
|
|
227
|
+
def exception_name
|
228
|
+
exception.cause.class.to_s
|
229
|
+
end
|
230
|
+
|
231
|
+
def message
|
232
|
+
exception.message
|
233
|
+
end
|
234
|
+
|
235
|
+
def exception_inspect
|
236
|
+
exception.inspect
|
237
|
+
end
|
238
|
+
|
239
|
+
def exception_id
|
240
|
+
exception.object_id
|
241
|
+
end
|
242
|
+
|
148
243
|
private
|
149
|
-
|
150
|
-
|
244
|
+
class SourceMapLocation < DelegateClass(Thread::Backtrace::Location) # :nodoc:
|
245
|
+
def initialize(location, template)
|
246
|
+
super(location)
|
247
|
+
@template = template
|
248
|
+
end
|
249
|
+
|
250
|
+
def spot(exc)
|
251
|
+
if RubyVM::AbstractSyntaxTree.respond_to?(:node_id_for_backtrace_location) && __getobj__.is_a?(Thread::Backtrace::Location)
|
252
|
+
location = @template.spot(__getobj__)
|
253
|
+
else
|
254
|
+
location = super
|
255
|
+
end
|
256
|
+
|
257
|
+
if location
|
258
|
+
@template.translate_location(__getobj__, location)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
attr_reader :backtrace
|
264
|
+
|
265
|
+
def build_backtrace
|
266
|
+
built_methods = {}
|
267
|
+
|
268
|
+
ActionView::PathRegistry.all_resolvers.each do |resolver|
|
269
|
+
resolver.built_templates.each do |template|
|
270
|
+
built_methods[template.method_name] = template
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
(@exception.backtrace_locations || []).map do |loc|
|
275
|
+
if built_methods.key?(loc.label.to_s)
|
276
|
+
thread_backtrace_location = if loc.respond_to?(:__getobj__)
|
277
|
+
loc.__getobj__
|
278
|
+
else
|
279
|
+
loc
|
280
|
+
end
|
281
|
+
SourceMapLocation.new(thread_backtrace_location, built_methods[loc.label.to_s])
|
282
|
+
else
|
283
|
+
loc
|
284
|
+
end
|
285
|
+
end
|
151
286
|
end
|
152
287
|
|
153
288
|
def causes_for(exception)
|
@@ -168,29 +303,53 @@ module ActionDispatch
|
|
168
303
|
end
|
169
304
|
end
|
170
305
|
|
306
|
+
def extract_source(trace)
|
307
|
+
spot = trace.spot(@exception)
|
308
|
+
|
309
|
+
if spot
|
310
|
+
line = spot[:first_lineno]
|
311
|
+
code = extract_source_fragment_lines(spot[:script_lines], line)
|
312
|
+
|
313
|
+
if line == spot[:last_lineno]
|
314
|
+
code[line] = [
|
315
|
+
code[line][0, spot[:first_column]],
|
316
|
+
code[line][spot[:first_column]...spot[:last_column]],
|
317
|
+
code[line][spot[:last_column]..-1],
|
318
|
+
]
|
319
|
+
end
|
320
|
+
|
321
|
+
return {
|
322
|
+
code: code,
|
323
|
+
line_number: line
|
324
|
+
}
|
325
|
+
end
|
326
|
+
|
327
|
+
file, line_number = extract_file_and_line_number(trace)
|
328
|
+
|
329
|
+
{
|
330
|
+
code: source_fragment(file, line_number),
|
331
|
+
line_number: line_number
|
332
|
+
}
|
333
|
+
end
|
334
|
+
|
335
|
+
def extract_source_fragment_lines(source_lines, line)
|
336
|
+
start = [line - 3, 0].max
|
337
|
+
lines = source_lines.drop(start).take(6)
|
338
|
+
Hash[*(start + 1..(lines.count + start)).zip(lines).flatten]
|
339
|
+
end
|
340
|
+
|
171
341
|
def source_fragment(path, line)
|
172
342
|
return unless Rails.respond_to?(:root) && Rails.root
|
173
343
|
full_path = Rails.root.join(path)
|
174
344
|
if File.exist?(full_path)
|
175
345
|
File.open(full_path, "r") do |file|
|
176
|
-
|
177
|
-
lines = file.each_line.drop(start).take(6)
|
178
|
-
Hash[*(start + 1..(lines.count + start)).zip(lines).flatten]
|
346
|
+
extract_source_fragment_lines(file.each_line, line)
|
179
347
|
end
|
180
348
|
end
|
181
349
|
end
|
182
350
|
|
183
351
|
def extract_file_and_line_number(trace)
|
184
|
-
|
185
|
-
# Windows and Unix path styles.
|
186
|
-
file, line = trace.match(/^(.+?):(\d+).*$/, &:captures) || trace
|
187
|
-
[file, line.to_i]
|
188
|
-
end
|
189
|
-
|
190
|
-
def expand_backtrace
|
191
|
-
@exception.backtrace.unshift(
|
192
|
-
@exception.to_s.split("\n")
|
193
|
-
).flatten!
|
352
|
+
[trace.path, trace.lineno]
|
194
353
|
end
|
195
354
|
end
|
196
355
|
end
|
@@ -14,7 +14,7 @@ module ActionDispatch
|
|
14
14
|
response = @app.call(env)
|
15
15
|
returned = response << ::Rack::BodyProxy.new(response.pop) { state.complete! }
|
16
16
|
rescue => error
|
17
|
-
@executor.error_reporter.report(error, handled: false)
|
17
|
+
@executor.error_reporter.report(error, handled: false, source: "application.action_dispatch")
|
18
18
|
raise
|
19
19
|
ensure
|
20
20
|
state.complete! unless returned
|
@@ -3,6 +3,8 @@
|
|
3
3
|
require "active_support/core_ext/hash/keys"
|
4
4
|
|
5
5
|
module ActionDispatch
|
6
|
+
# = Action Dispatch \Flash
|
7
|
+
#
|
6
8
|
# The flash provides a way to pass temporary primitive-types (String, Array, Hash) between actions. Anything you place in the flash will be exposed
|
7
9
|
# to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create
|
8
10
|
# action that sets <tt>flash[:notice] = "Post successfully created"</tt> before redirecting to a display action that can
|
@@ -175,6 +177,8 @@ module ActionDispatch
|
|
175
177
|
@flashes.key? name.to_s
|
176
178
|
end
|
177
179
|
|
180
|
+
# Immediately deletes the single flash entry. Use this method when you
|
181
|
+
# want remove the message within the current action. See also #discard.
|
178
182
|
def delete(key)
|
179
183
|
key = key.to_s
|
180
184
|
@discard.delete key
|
@@ -243,6 +247,9 @@ module ActionDispatch
|
|
243
247
|
#
|
244
248
|
# flash.discard # discard the entire flash at the end of the current action
|
245
249
|
# flash.discard(:warning) # discard only the "warning" entry at the end of the current action
|
250
|
+
#
|
251
|
+
# Use this method when you want to display the message in the current
|
252
|
+
# action but not in the next one. See also #delete.
|
246
253
|
def discard(k = nil)
|
247
254
|
k = k.to_s if k
|
248
255
|
@discard.merge Array(k || keys)
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActionDispatch
|
4
|
+
# = Action Dispatch \HostAuthorization
|
5
|
+
#
|
4
6
|
# This middleware guards from DNS rebinding attacks by explicitly permitting
|
5
7
|
# the hosts a request can be sent to, and is passed the options set in
|
6
8
|
# +config.host_authorization+.
|
@@ -18,6 +20,7 @@ module ActionDispatch
|
|
18
20
|
class HostAuthorization
|
19
21
|
ALLOWED_HOSTS_IN_DEVELOPMENT = [".localhost", IPAddr.new("0.0.0.0/0"), IPAddr.new("::/0")]
|
20
22
|
PORT_REGEX = /(?::\d+)/ # :nodoc:
|
23
|
+
SUBDOMAIN_REGEX = /(?:[a-z0-9-]+\.)/i # :nodoc:
|
21
24
|
IPV4_HOSTNAME = /(?<host>\d+\.\d+\.\d+\.\d+)#{PORT_REGEX}?/ # :nodoc:
|
22
25
|
IPV6_HOSTNAME = /(?<host>[a-f0-9]*:[a-f0-9.:]+)/i # :nodoc:
|
23
26
|
IPV6_HOSTNAME_WITH_PORT = /\[#{IPV6_HOSTNAME}\]#{PORT_REGEX}/i # :nodoc:
|
@@ -69,7 +72,7 @@ module ActionDispatch
|
|
69
72
|
|
70
73
|
def sanitize_string(host)
|
71
74
|
if host.start_with?(".")
|
72
|
-
/\A
|
75
|
+
/\A#{SUBDOMAIN_REGEX}?#{Regexp.escape(host[1..-1])}#{PORT_REGEX}?\z/i
|
73
76
|
else
|
74
77
|
/\A#{Regexp.escape host}#{PORT_REGEX}?\z/i
|
75
78
|
end
|
@@ -101,8 +104,8 @@ module ActionDispatch
|
|
101
104
|
|
102
105
|
def response(format, body)
|
103
106
|
[RESPONSE_STATUS,
|
104
|
-
{
|
105
|
-
|
107
|
+
{ Rack::CONTENT_TYPE => "#{format}; charset=#{Response.default_charset}",
|
108
|
+
Rack::CONTENT_LENGTH => body.bytesize.to_s },
|
106
109
|
[body]]
|
107
110
|
end
|
108
111
|
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActionDispatch
|
4
|
+
# = Action Dispatch \PublicExceptions
|
5
|
+
#
|
4
6
|
# When called, this middleware renders an error page. By default if an HTML
|
5
7
|
# response is expected it will render static error pages from the <tt>/public</tt>
|
6
8
|
# directory. For example when this middleware receives a 500 response it will
|
@@ -42,8 +44,8 @@ module ActionDispatch
|
|
42
44
|
end
|
43
45
|
|
44
46
|
def render_format(status, content_type, body)
|
45
|
-
[status, {
|
46
|
-
|
47
|
+
[status, { Rack::CONTENT_TYPE => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
|
48
|
+
Rack::CONTENT_LENGTH => body.bytesize.to_s }, [body]]
|
47
49
|
end
|
48
50
|
|
49
51
|
def render_html(status)
|
@@ -53,7 +55,7 @@ module ActionDispatch
|
|
53
55
|
if found || File.exist?(path)
|
54
56
|
render_format(status, "text/html", File.read(path))
|
55
57
|
else
|
56
|
-
[404, {
|
58
|
+
[404, { Constants::X_CASCADE => "pass" }, []]
|
57
59
|
end
|
58
60
|
end
|
59
61
|
end
|
@@ -1,12 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActionDispatch
|
4
|
-
#
|
5
|
-
# callbacks, intended to assist with code reloading during development.
|
4
|
+
# = Action Dispatch \Reloader
|
6
5
|
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
6
|
+
# ActionDispatch::Reloader wraps the request with callbacks provided by
|
7
|
+
# ActiveSupport::Reloader, intended to assist with code reloading during
|
8
|
+
# development.
|
9
|
+
#
|
10
|
+
# ActionDispatch::Reloader is included in the middleware stack only if
|
11
|
+
# reloading is enabled, which it is by the default in +development+ mode.
|
10
12
|
class Reloader < Executor
|
11
13
|
end
|
12
14
|
end
|
@@ -3,14 +3,14 @@
|
|
3
3
|
require "ipaddr"
|
4
4
|
|
5
5
|
module ActionDispatch
|
6
|
+
# = Action Dispatch \RemoteIp
|
7
|
+
#
|
6
8
|
# This middleware calculates the IP address of the remote client that is
|
7
9
|
# making the request. It does this by checking various headers that could
|
8
10
|
# contain the address, and then picking the last-set address that is not
|
9
11
|
# on the list of trusted IPs. This follows the precedent set by e.g.
|
10
|
-
# {the Tomcat server}[https://issues.apache.org/bugzilla/show_bug.cgi?id=50453]
|
11
|
-
#
|
12
|
-
# by @gingerlime. A more detailed explanation of the algorithm is given
|
13
|
-
# at GetIp#calculate_ip.
|
12
|
+
# {the Tomcat server}[https://issues.apache.org/bugzilla/show_bug.cgi?id=50453].
|
13
|
+
# A more detailed explanation of the algorithm is given 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
|
@@ -24,7 +24,8 @@ module ActionDispatch
|
|
24
24
|
# a proxy, because you are hosted on e.g. Heroku without SSL, any client can
|
25
25
|
# claim to have any IP address by setting the +X-Forwarded-For+ header. If you
|
26
26
|
# care about that, then you need to explicitly drop or ignore those headers
|
27
|
-
# sometime before this middleware runs.
|
27
|
+
# sometime before this middleware runs. Alternatively, remove this middleware
|
28
|
+
# to avoid inadvertently relying on it.
|
28
29
|
class RemoteIp
|
29
30
|
class IpSpoofAttackError < StandardError; end
|
30
31
|
|
@@ -65,9 +66,9 @@ module ActionDispatch
|
|
65
66
|
elsif custom_proxies.respond_to?(:any?)
|
66
67
|
custom_proxies
|
67
68
|
else
|
68
|
-
|
69
|
-
Setting config.action_dispatch.trusted_proxies to a single value
|
70
|
-
|
69
|
+
raise(ArgumentError, <<~EOM)
|
70
|
+
Setting config.action_dispatch.trusted_proxies to a single value isn't
|
71
|
+
supported. Please set this to an enumerable instead. For
|
71
72
|
example, instead of:
|
72
73
|
|
73
74
|
config.action_dispatch.trusted_proxies = IPAddr.new("10.0.0.0/8")
|
@@ -76,10 +77,8 @@ module ActionDispatch
|
|
76
77
|
|
77
78
|
config.action_dispatch.trusted_proxies = [IPAddr.new("10.0.0.0/8")]
|
78
79
|
|
79
|
-
Note that
|
80
|
-
will *replace* the default set of trusted proxies.
|
80
|
+
Note that passing an enumerable will *replace* the default set of trusted proxies.
|
81
81
|
EOM
|
82
|
-
Array(custom_proxies) + TRUSTED_PROXIES
|
83
82
|
end
|
84
83
|
end
|
85
84
|
|
@@ -114,7 +113,7 @@ module ActionDispatch
|
|
114
113
|
# proxies, that header may contain a list of IPs. Other proxy services
|
115
114
|
# set the +Client-Ip+ header instead, so we check that too.
|
116
115
|
#
|
117
|
-
# As discussed in {this post about Rails IP Spoofing}[https://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/],
|
116
|
+
# As discussed in {this post about Rails IP Spoofing}[https://web.archive.org/web/20170626095448/https://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/],
|
118
117
|
# while the first IP in the list is likely to be the "originating" IP,
|
119
118
|
# it could also have been set by the client maliciously.
|
120
119
|
#
|
@@ -126,8 +125,8 @@ module ActionDispatch
|
|
126
125
|
remote_addr = ips_from(@req.remote_addr).last
|
127
126
|
|
128
127
|
# Could be a CSV list and/or repeated headers that were concatenated.
|
129
|
-
client_ips = ips_from(@req.client_ip).reverse
|
130
|
-
forwarded_ips = ips_from(@req.x_forwarded_for).reverse
|
128
|
+
client_ips = ips_from(@req.client_ip).reverse!
|
129
|
+
forwarded_ips = ips_from(@req.x_forwarded_for).reverse!
|
131
130
|
|
132
131
|
# +Client-Ip+ and +X-Forwarded-For+ should not, generally, both be set.
|
133
132
|
# If they are both set, it means that either:
|
@@ -155,7 +154,8 @@ module ActionDispatch
|
|
155
154
|
# - X-Forwarded-For will be a list of IPs, one per proxy, or blank
|
156
155
|
# - Client-Ip is propagated from the outermost proxy, or is blank
|
157
156
|
# - REMOTE_ADDR will be the IP that made the request to Rack
|
158
|
-
ips =
|
157
|
+
ips = forwarded_ips + client_ips
|
158
|
+
ips.compact!
|
159
159
|
|
160
160
|
# If every single IP option is in the trusted list, return the IP
|
161
161
|
# that's furthest away
|
@@ -173,7 +173,7 @@ module ActionDispatch
|
|
173
173
|
return [] unless header
|
174
174
|
# Split the comma-separated list into an array of strings.
|
175
175
|
ips = header.strip.split(/[,\s]+/)
|
176
|
-
ips.select do |ip|
|
176
|
+
ips.select! do |ip|
|
177
177
|
# Only return IPs that are valid according to the IPAddr#new method.
|
178
178
|
range = IPAddr.new(ip).to_range
|
179
179
|
# We want to make sure nobody is sneaking a netmask in.
|
@@ -181,6 +181,7 @@ module ActionDispatch
|
|
181
181
|
rescue ArgumentError
|
182
182
|
nil
|
183
183
|
end
|
184
|
+
ips
|
184
185
|
end
|
185
186
|
|
186
187
|
def filter_proxies(ips) # :doc:
|