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.
Files changed (136) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +360 -353
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -2
  5. data/lib/abstract_controller/base.rb +20 -11
  6. data/lib/abstract_controller/caching/fragments.rb +2 -0
  7. data/lib/abstract_controller/callbacks.rb +31 -6
  8. data/lib/abstract_controller/deprecator.rb +7 -0
  9. data/lib/abstract_controller/helpers.rb +61 -18
  10. data/lib/abstract_controller/railties/routes_helpers.rb +1 -16
  11. data/lib/abstract_controller/rendering.rb +3 -3
  12. data/lib/abstract_controller/translation.rb +7 -4
  13. data/lib/abstract_controller/url_for.rb +2 -0
  14. data/lib/abstract_controller.rb +6 -0
  15. data/lib/action_controller/api.rb +5 -3
  16. data/lib/action_controller/base.rb +3 -17
  17. data/lib/action_controller/caching.rb +2 -0
  18. data/lib/action_controller/deprecator.rb +7 -0
  19. data/lib/action_controller/form_builder.rb +2 -0
  20. data/lib/action_controller/log_subscriber.rb +16 -4
  21. data/lib/action_controller/metal/content_security_policy.rb +1 -1
  22. data/lib/action_controller/metal/data_streaming.rb +2 -0
  23. data/lib/action_controller/metal/default_headers.rb +2 -0
  24. data/lib/action_controller/metal/etag_with_flash.rb +2 -0
  25. data/lib/action_controller/metal/etag_with_template_digest.rb +2 -0
  26. data/lib/action_controller/metal/exceptions.rb +8 -0
  27. data/lib/action_controller/metal/head.rb +8 -6
  28. data/lib/action_controller/metal/helpers.rb +3 -14
  29. data/lib/action_controller/metal/http_authentication.rb +17 -8
  30. data/lib/action_controller/metal/implicit_render.rb +5 -3
  31. data/lib/action_controller/metal/instrumentation.rb +8 -1
  32. data/lib/action_controller/metal/live.rb +24 -0
  33. data/lib/action_controller/metal/mime_responds.rb +2 -2
  34. data/lib/action_controller/metal/params_wrapper.rb +4 -2
  35. data/lib/action_controller/metal/permissions_policy.rb +1 -1
  36. data/lib/action_controller/metal/redirecting.rb +7 -7
  37. data/lib/action_controller/metal/renderers.rb +2 -2
  38. data/lib/action_controller/metal/rendering.rb +0 -7
  39. data/lib/action_controller/metal/request_forgery_protection.rb +139 -50
  40. data/lib/action_controller/metal/rescue.rb +2 -0
  41. data/lib/action_controller/metal/streaming.rb +70 -30
  42. data/lib/action_controller/metal/strong_parameters.rb +132 -52
  43. data/lib/action_controller/metal/url_for.rb +7 -0
  44. data/lib/action_controller/metal.rb +79 -21
  45. data/lib/action_controller/railtie.rb +22 -9
  46. data/lib/action_controller/renderer.rb +98 -65
  47. data/lib/action_controller/test_case.rb +15 -5
  48. data/lib/action_controller.rb +8 -1
  49. data/lib/action_dispatch/constants.rb +32 -0
  50. data/lib/action_dispatch/deprecator.rb +7 -0
  51. data/lib/action_dispatch/http/cache.rb +1 -3
  52. data/lib/action_dispatch/http/content_security_policy.rb +9 -8
  53. data/lib/action_dispatch/http/filter_parameters.rb +11 -5
  54. data/lib/action_dispatch/http/headers.rb +2 -0
  55. data/lib/action_dispatch/http/mime_negotiation.rb +22 -22
  56. data/lib/action_dispatch/http/mime_type.rb +35 -12
  57. data/lib/action_dispatch/http/mime_types.rb +3 -1
  58. data/lib/action_dispatch/http/parameters.rb +1 -1
  59. data/lib/action_dispatch/http/permissions_policy.rb +40 -18
  60. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  61. data/lib/action_dispatch/http/request.rb +48 -14
  62. data/lib/action_dispatch/http/response.rb +80 -59
  63. data/lib/action_dispatch/http/upload.rb +2 -0
  64. data/lib/action_dispatch/journey/formatter.rb +8 -2
  65. data/lib/action_dispatch/journey/path/pattern.rb +14 -14
  66. data/lib/action_dispatch/journey/route.rb +3 -2
  67. data/lib/action_dispatch/journey/router.rb +9 -8
  68. data/lib/action_dispatch/journey/routes.rb +2 -2
  69. data/lib/action_dispatch/log_subscriber.rb +23 -0
  70. data/lib/action_dispatch/middleware/actionable_exceptions.rb +5 -6
  71. data/lib/action_dispatch/middleware/assume_ssl.rb +24 -0
  72. data/lib/action_dispatch/middleware/callbacks.rb +2 -0
  73. data/lib/action_dispatch/middleware/cookies.rb +81 -98
  74. data/lib/action_dispatch/middleware/debug_exceptions.rb +26 -25
  75. data/lib/action_dispatch/middleware/debug_locks.rb +4 -1
  76. data/lib/action_dispatch/middleware/debug_view.rb +7 -2
  77. data/lib/action_dispatch/middleware/exception_wrapper.rb +186 -27
  78. data/lib/action_dispatch/middleware/executor.rb +1 -1
  79. data/lib/action_dispatch/middleware/flash.rb +7 -0
  80. data/lib/action_dispatch/middleware/host_authorization.rb +6 -3
  81. data/lib/action_dispatch/middleware/public_exceptions.rb +5 -3
  82. data/lib/action_dispatch/middleware/reloader.rb +7 -5
  83. data/lib/action_dispatch/middleware/remote_ip.rb +17 -16
  84. data/lib/action_dispatch/middleware/request_id.rb +2 -0
  85. data/lib/action_dispatch/middleware/server_timing.rb +4 -4
  86. data/lib/action_dispatch/middleware/session/abstract_store.rb +5 -0
  87. data/lib/action_dispatch/middleware/session/cache_store.rb +2 -0
  88. data/lib/action_dispatch/middleware/session/cookie_store.rb +11 -5
  89. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +3 -1
  90. data/lib/action_dispatch/middleware/show_exceptions.rb +19 -15
  91. data/lib/action_dispatch/middleware/ssl.rb +18 -6
  92. data/lib/action_dispatch/middleware/stack.rb +7 -2
  93. data/lib/action_dispatch/middleware/static.rb +12 -8
  94. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
  95. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +4 -4
  96. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
  97. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +7 -7
  98. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
  99. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +17 -0
  100. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +16 -12
  101. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -1
  102. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
  103. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +4 -4
  104. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  105. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  106. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +3 -0
  107. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +46 -37
  108. data/lib/action_dispatch/railtie.rb +14 -4
  109. data/lib/action_dispatch/request/session.rb +16 -6
  110. data/lib/action_dispatch/request/utils.rb +8 -3
  111. data/lib/action_dispatch/routing/inspector.rb +54 -6
  112. data/lib/action_dispatch/routing/mapper.rb +35 -24
  113. data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -0
  114. data/lib/action_dispatch/routing/redirection.rb +15 -6
  115. data/lib/action_dispatch/routing/route_set.rb +52 -22
  116. data/lib/action_dispatch/routing/routes_proxy.rb +10 -15
  117. data/lib/action_dispatch/routing/url_for.rb +5 -1
  118. data/lib/action_dispatch/routing.rb +7 -7
  119. data/lib/action_dispatch/system_test_case.rb +3 -3
  120. data/lib/action_dispatch/system_testing/browser.rb +20 -19
  121. data/lib/action_dispatch/system_testing/driver.rb +13 -21
  122. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +27 -16
  123. data/lib/action_dispatch/testing/assertion_response.rb +1 -1
  124. data/lib/action_dispatch/testing/assertions/response.rb +13 -6
  125. data/lib/action_dispatch/testing/assertions/routing.rb +67 -28
  126. data/lib/action_dispatch/testing/assertions.rb +3 -1
  127. data/lib/action_dispatch/testing/integration.rb +27 -17
  128. data/lib/action_dispatch/testing/request_encoder.rb +4 -1
  129. data/lib/action_dispatch/testing/test_process.rb +4 -3
  130. data/lib/action_dispatch/testing/test_request.rb +1 -1
  131. data/lib/action_dispatch/testing/test_response.rb +23 -9
  132. data/lib/action_dispatch.rb +37 -4
  133. data/lib/action_pack/gem_version.rb +4 -4
  134. data/lib/action_pack/version.rb +1 -1
  135. data/lib/action_pack.rb +1 -1
  136. 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["X-Cascade"] == "pass"
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
- invoke_interceptors(request, exception)
38
- raise exception unless request.show_exceptions?
39
- render_exception(request, exception)
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.exception.inspect,
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.exception),
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, { "Content-Type" => "#{format}; charset=#{Response.default_charset}", "Content-Length" => body.bytesize.to_s }, [body]]
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 << "#{exception.class} (#{exception.message}):"
145
- message.concat(exception.annotated_source_code) if exception.respond_to?(:annotated_source_code)
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.fatal lines.join("\n#{logger.formatter.tags_text}")
157
+ logger.add(level, lines.join("\n#{logger.formatter.tags_text}"))
157
158
  else
158
- logger.fatal lines.join("\n")
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.is_a?(ActionController::RoutingError) || exception.is_a?(ActionView::Template::Error))
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, { "Content-Type" => "text/plain", "Content-Length" => str.size }, [str]]
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
- RESCUES_TEMPLATE_PATH = File.expand_path("templates", __dir__)
10
+ RESCUES_TEMPLATE_PATHS = [File.expand_path("templates", __dir__)]
11
11
 
12
12
  def initialize(assigns)
13
- paths = [RESCUES_TEMPLATE_PATH]
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, :exception, :wrapped_causes, :line_number, :file
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
- @exception = exception
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
- expand_backtrace if exception.is_a?(SyntaxError) || exception.cause.is_a?(SyntaxError)
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
- exception
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
- file, line_number = extract_file_and_line_number(trace)
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
- def backtrace
150
- Array(@exception.backtrace)
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
- start = [line - 3, 0].max
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
- # Split by the first colon followed by some digits, which works for both
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([a-z0-9-]+\.)?#{Regexp.escape(host[1..-1])}#{PORT_REGEX}?\z/i
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
- { "Content-Type" => "#{format}; charset=#{Response.default_charset}",
105
- "Content-Length" => body.bytesize.to_s },
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, { "Content-Type" => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
46
- "Content-Length" => body.bytesize.to_s }, [body]]
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, { "X-Cascade" => "pass" }, []]
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
- # ActionDispatch::Reloader wraps the request with callbacks provided by ActiveSupport::Reloader
5
- # callbacks, intended to assist with code reloading during development.
4
+ # = Action Dispatch \Reloader
6
5
  #
7
- # By default, ActionDispatch::Reloader is included in the middleware stack
8
- # only in the development environment; specifically, when +config.cache_classes+
9
- # is false.
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
- # with {reasoning explained at length}[https://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection]
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
- ActiveSupport::Deprecation.warn(<<~EOM)
69
- Setting config.action_dispatch.trusted_proxies to a single value has
70
- been deprecated. Please set this to an enumerable instead. For
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 unlike passing a single argument, passing an enumerable
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 = [forwarded_ips, client_ips].flatten.compact
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: