actionpack 7.0.4 → 7.1.3.4
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 +397 -269
- data/MIT-LICENSE +1 -1
- data/README.rdoc +4 -4
- 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 +75 -28
- data/lib/abstract_controller/railties/routes_helpers.rb +1 -16
- data/lib/abstract_controller/rendering.rb +12 -14
- data/lib/abstract_controller/translation.rb +9 -6
- data/lib/abstract_controller/url_for.rb +2 -0
- data/lib/abstract_controller.rb +6 -0
- data/lib/action_controller/api.rb +6 -4
- 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/basic_implicit_render.rb +3 -1
- data/lib/action_controller/metal/conditional_get.rb +121 -123
- data/lib/action_controller/metal/content_security_policy.rb +5 -5
- data/lib/action_controller/metal/data_streaming.rb +20 -18
- data/lib/action_controller/metal/default_headers.rb +2 -0
- data/lib/action_controller/metal/etag_with_flash.rb +3 -1
- 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 +9 -7
- 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 +25 -1
- 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 +2 -2
- data/lib/action_controller/metal/redirecting.rb +29 -8
- data/lib/action_controller/metal/renderers.rb +4 -4
- data/lib/action_controller/metal/rendering.rb +114 -9
- data/lib/action_controller/metal/request_forgery_protection.rb +144 -53
- data/lib/action_controller/metal/rescue.rb +6 -3
- data/lib/action_controller/metal/streaming.rb +71 -31
- data/lib/action_controller/metal/strong_parameters.rb +158 -101
- data/lib/action_controller/metal/url_for.rb +9 -4
- data/lib/action_controller/metal.rb +79 -21
- data/lib/action_controller/railtie.rb +24 -10
- data/lib/action_controller/renderer.rb +99 -85
- 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 +9 -11
- data/lib/action_dispatch/http/content_security_policy.rb +14 -9
- data/lib/action_dispatch/http/filter_parameters.rb +14 -28
- data/lib/action_dispatch/http/headers.rb +3 -1
- 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 +38 -23
- data/lib/action_dispatch/http/rack_cache.rb +2 -0
- data/lib/action_dispatch/http/request.rb +63 -30
- data/lib/action_dispatch/http/response.rb +80 -63
- data/lib/action_dispatch/http/upload.rb +15 -2
- 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 +108 -117
- 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 +18 -8
- 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 +21 -20
- data/lib/action_dispatch/middleware/request_id.rb +4 -2
- 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 +25 -18
- 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 +14 -10
- 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/blocked_host.html.erb +7 -3
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -3
- 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 +59 -41
- 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 +58 -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 +26 -22
- 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 +14 -22
- 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 +14 -7
- 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 +65 -29
@@ -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
|
@@ -95,14 +98,14 @@ module ActionDispatch
|
|
95
98
|
def response_body(request)
|
96
99
|
return "" unless request.get_header("action_dispatch.show_detailed_exceptions")
|
97
100
|
|
98
|
-
template = DebugView.new(
|
101
|
+
template = DebugView.new(hosts: request.env["action_dispatch.blocked_hosts"])
|
99
102
|
template.render(template: "rescues/blocked_host", layout: "rescues/layout")
|
100
103
|
end
|
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
|
|
@@ -111,7 +114,7 @@ module ActionDispatch
|
|
111
114
|
|
112
115
|
return unless logger
|
113
116
|
|
114
|
-
logger.error("[#{self.class.name}] Blocked
|
117
|
+
logger.error("[#{self.class.name}] Blocked hosts: #{request.env["action_dispatch.blocked_hosts"].join(", ")}")
|
115
118
|
end
|
116
119
|
|
117
120
|
def available_logger(request)
|
@@ -131,21 +134,28 @@ module ActionDispatch
|
|
131
134
|
return @app.call(env) if @permissions.empty?
|
132
135
|
|
133
136
|
request = Request.new(env)
|
137
|
+
hosts = blocked_hosts(request)
|
134
138
|
|
135
|
-
if
|
139
|
+
if hosts.empty? || excluded?(request)
|
136
140
|
mark_as_authorized(request)
|
137
141
|
@app.call(env)
|
138
142
|
else
|
143
|
+
env["action_dispatch.blocked_hosts"] = hosts
|
139
144
|
@response_app.call(env)
|
140
145
|
end
|
141
146
|
end
|
142
147
|
|
143
148
|
private
|
144
|
-
def
|
149
|
+
def blocked_hosts(request)
|
150
|
+
hosts = []
|
151
|
+
|
145
152
|
origin_host = request.get_header("HTTP_HOST")
|
153
|
+
hosts << origin_host unless @permissions.allows?(origin_host)
|
154
|
+
|
146
155
|
forwarded_host = request.x_forwarded_host&.split(/,\s?/)&.last
|
156
|
+
hosts << forwarded_host unless forwarded_host.blank? || @permissions.allows?(forwarded_host)
|
147
157
|
|
148
|
-
|
158
|
+
hosts
|
149
159
|
end
|
150
160
|
|
151
161
|
def excluded?(request)
|
@@ -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
|
@@ -22,9 +22,10 @@ module ActionDispatch
|
|
22
22
|
# This middleware assumes that there is at least one proxy sitting around
|
23
23
|
# and setting headers with the client's remote IP address. If you don't use
|
24
24
|
# a proxy, because you are hosted on e.g. Heroku without SSL, any client can
|
25
|
-
# claim to have any IP address by setting the X-Forwarded-For header. If you
|
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
|
|
@@ -53,7 +54,7 @@ module ActionDispatch
|
|
53
54
|
#
|
54
55
|
# The +custom_proxies+ argument can take an enumerable which will be used
|
55
56
|
# instead of +TRUSTED_PROXIES+. Any proxy setup will put the value you
|
56
|
-
# want in the middle (or at the beginning) of the X-Forwarded-For list,
|
57
|
+
# want in the middle (or at the beginning) of the +X-Forwarded-For+ list,
|
57
58
|
# with your proxy servers after it. If your proxies aren't removed, pass
|
58
59
|
# them in via the +custom_proxies+ parameter. That way, the middleware will
|
59
60
|
# ignore those IP addresses, and return the one that you want.
|
@@ -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
|
|
@@ -110,11 +109,11 @@ module ActionDispatch
|
|
110
109
|
# REMOTE_ADDR will be correct if the request is made directly against the
|
111
110
|
# Ruby process, on e.g. Heroku. When the request is proxied by another
|
112
111
|
# server like HAProxy or NGINX, the IP address that made the original
|
113
|
-
# request will be put in an X-Forwarded-For header. If there are multiple
|
112
|
+
# request will be put in an +X-Forwarded-For+ header. If there are multiple
|
114
113
|
# proxies, that header may contain a list of IPs. Other proxy services
|
115
|
-
# set the Client-Ip header instead, so we check that too.
|
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:
|
@@ -4,11 +4,13 @@ require "securerandom"
|
|
4
4
|
require "active_support/core_ext/string/access"
|
5
5
|
|
6
6
|
module ActionDispatch
|
7
|
+
# = Action Dispatch \RequestId
|
8
|
+
#
|
7
9
|
# Makes a unique request id available to the +action_dispatch.request_id+ env variable (which is then accessible
|
8
10
|
# through ActionDispatch::Request#request_id or the alias ActionDispatch::Request#uuid) and sends
|
9
|
-
# the same id to the client via the X-Request-Id header.
|
11
|
+
# the same id to the client via the +X-Request-Id+ header.
|
10
12
|
#
|
11
|
-
# The unique request id is either based on the X-Request-Id header in the request, which would typically be generated
|
13
|
+
# The unique request id is either based on the +X-Request-Id+ header in the request, which would typically be generated
|
12
14
|
# by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the
|
13
15
|
# header is accepted from the outside world, we sanitize it to a max of 255 chars and alphanumeric and dashes only.
|
14
16
|
#
|
@@ -4,8 +4,6 @@ require "active_support/notifications"
|
|
4
4
|
|
5
5
|
module ActionDispatch
|
6
6
|
class ServerTiming
|
7
|
-
SERVER_TIMING_HEADER = "Server-Timing"
|
8
|
-
|
9
7
|
class Subscriber # :nodoc:
|
10
8
|
include Singleton
|
11
9
|
KEY = :action_dispatch_server_timing_events
|
@@ -67,8 +65,10 @@ module ActionDispatch
|
|
67
65
|
"%s;dur=%.2f" % [event_name, events_collection.sum(&:duration)]
|
68
66
|
end
|
69
67
|
|
70
|
-
|
71
|
-
|
68
|
+
if headers[ActionDispatch::Constants::SERVER_TIMING].present?
|
69
|
+
header_info.prepend(headers[ActionDispatch::Constants::SERVER_TIMING])
|
70
|
+
end
|
71
|
+
headers[ActionDispatch::Constants::SERVER_TIMING] = header_info.join(", ")
|
72
72
|
|
73
73
|
response
|
74
74
|
end
|
@@ -4,6 +4,8 @@ require "action_dispatch/middleware/session/abstract_store"
|
|
4
4
|
|
5
5
|
module ActionDispatch
|
6
6
|
module Session
|
7
|
+
# = Action Dispatch Session \CacheStore
|
8
|
+
#
|
7
9
|
# A session store that uses an ActiveSupport::Cache::Store to store the sessions. This store is most useful
|
8
10
|
# if you don't store critical data in your sessions and you don't need them to live for extended periods
|
9
11
|
# of time.
|
@@ -6,7 +6,9 @@ require "rack/session/cookie"
|
|
6
6
|
|
7
7
|
module ActionDispatch
|
8
8
|
module Session
|
9
|
-
#
|
9
|
+
# = Action Dispatch Session \CookieStore
|
10
|
+
#
|
11
|
+
# This cookie-based session store is the \Rails default. It is
|
10
12
|
# dramatically faster than the alternatives.
|
11
13
|
#
|
12
14
|
# Sessions typically contain at most a user ID and flash message; both fit
|
@@ -18,18 +20,18 @@ module ActionDispatch
|
|
18
20
|
#
|
19
21
|
# Your cookies will be encrypted using your application's +secret_key_base+. This
|
20
22
|
# goes a step further than signed cookies in that encrypted cookies cannot
|
21
|
-
# be altered or read by users. This is the default starting in Rails 4.
|
23
|
+
# be altered or read by users. This is the default starting in \Rails 4.
|
22
24
|
#
|
23
25
|
# Configure your session store in an initializer:
|
24
26
|
#
|
25
27
|
# Rails.application.config.session_store :cookie_store, key: '_your_app_session'
|
26
28
|
#
|
27
29
|
# 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/
|
30
|
+
# generated by \Rails and stored in a temporary file in <tt>tmp/local_secret.txt</tt>.
|
29
31
|
# In all other environments, it is stored encrypted in the
|
30
32
|
# <tt>config/credentials.yml.enc</tt> file.
|
31
33
|
#
|
32
|
-
# If your application was not updated to Rails 5.2 defaults, the +secret_key_base+
|
34
|
+
# If your application was not updated to \Rails 5.2 defaults, the +secret_key_base+
|
33
35
|
# will be found in the old <tt>config/secrets.yml</tt> file.
|
34
36
|
#
|
35
37
|
# Note that changing your +secret_key_base+ will invalidate all existing session.
|
@@ -56,8 +58,12 @@ module ActionDispatch
|
|
56
58
|
end
|
57
59
|
end
|
58
60
|
|
61
|
+
DEFAULT_SAME_SITE = proc { |request| request.cookies_same_site_protection } # :nodoc:
|
62
|
+
|
59
63
|
def initialize(app, options = {})
|
60
|
-
|
64
|
+
options[:cookie_only] = true
|
65
|
+
options[:same_site] = DEFAULT_SAME_SITE if !options.key?(:same_site)
|
66
|
+
super
|
61
67
|
end
|
62
68
|
|
63
69
|
def delete_session(req, session_id, options)
|