actionpack 4.2.11.1 → 6.0.3

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.

Files changed (182) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +212 -526
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +9 -9
  5. data/lib/abstract_controller/asset_paths.rb +2 -0
  6. data/lib/abstract_controller/base.rb +47 -50
  7. data/lib/{action_controller → abstract_controller}/caching/fragments.rb +64 -17
  8. data/lib/abstract_controller/caching.rb +66 -0
  9. data/lib/abstract_controller/callbacks.rb +59 -31
  10. data/lib/abstract_controller/collector.rb +9 -13
  11. data/lib/abstract_controller/error.rb +6 -0
  12. data/lib/abstract_controller/helpers.rb +31 -30
  13. data/lib/abstract_controller/logger.rb +2 -0
  14. data/lib/abstract_controller/railties/routes_helpers.rb +5 -3
  15. data/lib/abstract_controller/rendering.rb +42 -41
  16. data/lib/abstract_controller/translation.rb +12 -9
  17. data/lib/abstract_controller/url_for.rb +2 -0
  18. data/lib/abstract_controller.rb +12 -5
  19. data/lib/action_controller/api/api_rendering.rb +16 -0
  20. data/lib/action_controller/api.rb +150 -0
  21. data/lib/action_controller/base.rb +25 -22
  22. data/lib/action_controller/caching.rb +13 -57
  23. data/lib/action_controller/form_builder.rb +50 -0
  24. data/lib/action_controller/log_subscriber.rb +15 -17
  25. data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
  26. data/lib/action_controller/metal/conditional_get.rb +124 -44
  27. data/lib/action_controller/metal/content_security_policy.rb +51 -0
  28. data/lib/action_controller/metal/cookies.rb +3 -3
  29. data/lib/action_controller/metal/data_streaming.rb +29 -49
  30. data/lib/action_controller/metal/default_headers.rb +17 -0
  31. data/lib/action_controller/metal/etag_with_flash.rb +18 -0
  32. data/lib/action_controller/metal/etag_with_template_digest.rb +20 -13
  33. data/lib/action_controller/metal/exceptions.rb +30 -15
  34. data/lib/action_controller/metal/flash.rb +9 -8
  35. data/lib/action_controller/metal/force_ssl.rb +23 -62
  36. data/lib/action_controller/metal/head.rb +22 -20
  37. data/lib/action_controller/metal/helpers.rb +26 -17
  38. data/lib/action_controller/metal/http_authentication.rb +76 -70
  39. data/lib/action_controller/metal/implicit_render.rb +53 -9
  40. data/lib/action_controller/metal/instrumentation.rb +22 -27
  41. data/lib/action_controller/metal/live.rb +101 -119
  42. data/lib/action_controller/metal/mime_responds.rb +44 -46
  43. data/lib/action_controller/metal/parameter_encoding.rb +51 -0
  44. data/lib/action_controller/metal/params_wrapper.rb +74 -63
  45. data/lib/action_controller/metal/redirecting.rb +53 -32
  46. data/lib/action_controller/metal/renderers.rb +87 -44
  47. data/lib/action_controller/metal/rendering.rb +72 -51
  48. data/lib/action_controller/metal/request_forgery_protection.rb +217 -97
  49. data/lib/action_controller/metal/rescue.rb +9 -16
  50. data/lib/action_controller/metal/streaming.rb +12 -11
  51. data/lib/action_controller/metal/strong_parameters.rb +619 -183
  52. data/lib/action_controller/metal/testing.rb +2 -17
  53. data/lib/action_controller/metal/url_for.rb +19 -10
  54. data/lib/action_controller/metal.rb +104 -87
  55. data/lib/action_controller/railtie.rb +28 -10
  56. data/lib/action_controller/railties/helpers.rb +3 -1
  57. data/lib/action_controller/renderer.rb +130 -0
  58. data/lib/action_controller/template_assertions.rb +11 -0
  59. data/lib/action_controller/test_case.rb +286 -418
  60. data/lib/action_controller.rb +33 -21
  61. data/lib/action_dispatch/http/cache.rb +100 -51
  62. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  63. data/lib/action_dispatch/http/content_security_policy.rb +282 -0
  64. data/lib/action_dispatch/http/filter_parameters.rb +31 -24
  65. data/lib/action_dispatch/http/filter_redirect.rb +10 -12
  66. data/lib/action_dispatch/http/headers.rb +54 -22
  67. data/lib/action_dispatch/http/mime_negotiation.rb +61 -45
  68. data/lib/action_dispatch/http/mime_type.rb +141 -122
  69. data/lib/action_dispatch/http/mime_types.rb +20 -6
  70. data/lib/action_dispatch/http/parameter_filter.rb +8 -68
  71. data/lib/action_dispatch/http/parameters.rb +107 -39
  72. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  73. data/lib/action_dispatch/http/request.rb +204 -117
  74. data/lib/action_dispatch/http/response.rb +248 -114
  75. data/lib/action_dispatch/http/upload.rb +21 -7
  76. data/lib/action_dispatch/http/url.rb +181 -100
  77. data/lib/action_dispatch/journey/formatter.rb +56 -34
  78. data/lib/action_dispatch/journey/gtg/builder.rb +7 -6
  79. data/lib/action_dispatch/journey/gtg/simulator.rb +3 -9
  80. data/lib/action_dispatch/journey/gtg/transition_table.rb +17 -17
  81. data/lib/action_dispatch/journey/nfa/builder.rb +5 -3
  82. data/lib/action_dispatch/journey/nfa/dot.rb +13 -13
  83. data/lib/action_dispatch/journey/nfa/simulator.rb +3 -3
  84. data/lib/action_dispatch/journey/nfa/transition_table.rb +5 -49
  85. data/lib/action_dispatch/journey/nodes/node.rb +25 -12
  86. data/lib/action_dispatch/journey/parser.rb +23 -22
  87. data/lib/action_dispatch/journey/parser.y +3 -2
  88. data/lib/action_dispatch/journey/parser_extras.rb +12 -4
  89. data/lib/action_dispatch/journey/path/pattern.rb +55 -46
  90. data/lib/action_dispatch/journey/route.rb +107 -28
  91. data/lib/action_dispatch/journey/router/utils.rb +25 -16
  92. data/lib/action_dispatch/journey/router.rb +35 -27
  93. data/lib/action_dispatch/journey/routes.rb +17 -17
  94. data/lib/action_dispatch/journey/scanner.rb +26 -17
  95. data/lib/action_dispatch/journey/visitors.rb +98 -54
  96. data/lib/action_dispatch/journey.rb +7 -5
  97. data/lib/action_dispatch/middleware/actionable_exceptions.rb +39 -0
  98. data/lib/action_dispatch/middleware/callbacks.rb +3 -6
  99. data/lib/action_dispatch/middleware/cookies.rb +292 -203
  100. data/lib/action_dispatch/middleware/debug_exceptions.rb +142 -63
  101. data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
  102. data/lib/action_dispatch/middleware/debug_view.rb +66 -0
  103. data/lib/action_dispatch/middleware/exception_wrapper.rb +102 -70
  104. data/lib/action_dispatch/middleware/executor.rb +21 -0
  105. data/lib/action_dispatch/middleware/flash.rb +78 -54
  106. data/lib/action_dispatch/middleware/host_authorization.rb +101 -0
  107. data/lib/action_dispatch/middleware/public_exceptions.rb +32 -27
  108. data/lib/action_dispatch/middleware/reloader.rb +5 -91
  109. data/lib/action_dispatch/middleware/remote_ip.rb +48 -41
  110. data/lib/action_dispatch/middleware/request_id.rb +17 -9
  111. data/lib/action_dispatch/middleware/session/abstract_store.rb +41 -26
  112. data/lib/action_dispatch/middleware/session/cache_store.rb +24 -14
  113. data/lib/action_dispatch/middleware/session/cookie_store.rb +72 -73
  114. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -2
  115. data/lib/action_dispatch/middleware/show_exceptions.rb +26 -23
  116. data/lib/action_dispatch/middleware/ssl.rb +113 -35
  117. data/lib/action_dispatch/middleware/stack.rb +64 -41
  118. data/lib/action_dispatch/middleware/static.rb +57 -51
  119. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  121. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +4 -14
  122. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  123. data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +4 -2
  124. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  125. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +45 -35
  126. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -0
  128. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +26 -4
  129. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
  130. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +15 -0
  132. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +5 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +2 -2
  136. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -1
  137. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +3 -3
  138. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  139. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
  140. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +67 -64
  141. data/lib/action_dispatch/railtie.rb +26 -13
  142. data/lib/action_dispatch/request/session.rb +114 -60
  143. data/lib/action_dispatch/request/utils.rb +67 -24
  144. data/lib/action_dispatch/routing/endpoint.rb +9 -2
  145. data/lib/action_dispatch/routing/inspector.rb +140 -102
  146. data/lib/action_dispatch/routing/mapper.rb +762 -455
  147. data/lib/action_dispatch/routing/polymorphic_routes.rb +161 -142
  148. data/lib/action_dispatch/routing/redirection.rb +36 -26
  149. data/lib/action_dispatch/routing/route_set.rb +322 -298
  150. data/lib/action_dispatch/routing/routes_proxy.rb +32 -5
  151. data/lib/action_dispatch/routing/url_for.rb +65 -26
  152. data/lib/action_dispatch/routing.rb +36 -36
  153. data/lib/action_dispatch/system_test_case.rb +185 -0
  154. data/lib/action_dispatch/system_testing/browser.rb +80 -0
  155. data/lib/action_dispatch/system_testing/driver.rb +68 -0
  156. data/lib/action_dispatch/system_testing/server.rb +31 -0
  157. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +97 -0
  158. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +32 -0
  159. data/lib/action_dispatch/testing/assertion_response.rb +46 -0
  160. data/lib/action_dispatch/testing/assertions/response.rb +44 -20
  161. data/lib/action_dispatch/testing/assertions/routing.rb +44 -28
  162. data/lib/action_dispatch/testing/assertions.rb +6 -4
  163. data/lib/action_dispatch/testing/integration.rb +375 -215
  164. data/lib/action_dispatch/testing/request_encoder.rb +55 -0
  165. data/lib/action_dispatch/testing/test_process.rb +28 -22
  166. data/lib/action_dispatch/testing/test_request.rb +27 -34
  167. data/lib/action_dispatch/testing/test_response.rb +11 -11
  168. data/lib/action_dispatch.rb +33 -20
  169. data/lib/action_pack/gem_version.rb +6 -4
  170. data/lib/action_pack/version.rb +3 -1
  171. data/lib/action_pack.rb +4 -2
  172. metadata +71 -40
  173. data/lib/action_controller/metal/hide_actions.rb +0 -40
  174. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  175. data/lib/action_controller/middleware.rb +0 -39
  176. data/lib/action_controller/model_naming.rb +0 -12
  177. data/lib/action_dispatch/journey/backwards.rb +0 -5
  178. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  179. data/lib/action_dispatch/middleware/params_parser.rb +0 -60
  180. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  181. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  182. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -1,110 +1,189 @@
1
- require 'action_dispatch/http/request'
2
- require 'action_dispatch/middleware/exception_wrapper'
3
- require 'action_dispatch/routing/inspector'
1
+ # frozen_string_literal: true
2
+
3
+ require "action_dispatch/http/request"
4
+ require "action_dispatch/middleware/exception_wrapper"
5
+ require "action_dispatch/routing/inspector"
6
+
7
+ require "active_support/actionable_error"
8
+
9
+ require "action_view"
10
+ require "action_view/base"
4
11
 
5
12
  module ActionDispatch
6
13
  # This middleware is responsible for logging exceptions and
7
14
  # showing a debugging page in case the request is local.
8
15
  class DebugExceptions
9
- RESCUES_TEMPLATE_PATH = File.expand_path('../templates', __FILE__)
16
+ cattr_reader :interceptors, instance_accessor: false, default: []
10
17
 
11
- def initialize(app, routes_app = nil)
12
- @app = app
13
- @routes_app = routes_app
18
+ def self.register_interceptor(object = nil, &block)
19
+ interceptor = object || block
20
+ interceptors << interceptor
21
+ end
22
+
23
+ def initialize(app, routes_app = nil, response_format = :default, interceptors = self.class.interceptors)
24
+ @app = app
25
+ @routes_app = routes_app
26
+ @response_format = response_format
27
+ @interceptors = interceptors
14
28
  end
15
29
 
16
30
  def call(env)
31
+ request = ActionDispatch::Request.new env
17
32
  _, headers, body = response = @app.call(env)
18
33
 
19
- if headers['X-Cascade'] == 'pass'
34
+ if headers["X-Cascade"] == "pass"
20
35
  body.close if body.respond_to?(:close)
21
36
  raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
22
37
  end
23
38
 
24
39
  response
25
40
  rescue Exception => exception
26
- raise exception if env['action_dispatch.show_exceptions'] == false
27
- render_exception(env, exception)
41
+ invoke_interceptors(request, exception)
42
+ raise exception unless request.show_exceptions?
43
+ render_exception(request, exception)
28
44
  end
29
45
 
30
46
  private
47
+ def invoke_interceptors(request, exception)
48
+ backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
49
+ wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
50
+
51
+ @interceptors.each do |interceptor|
52
+ interceptor.call(request, exception)
53
+ rescue Exception
54
+ log_error(request, wrapper)
55
+ end
56
+ end
31
57
 
32
- def render_exception(env, exception)
33
- wrapper = ExceptionWrapper.new(env, exception)
34
- log_error(env, wrapper)
58
+ def render_exception(request, exception)
59
+ backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
60
+ wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
61
+ log_error(request, wrapper)
62
+
63
+ if request.get_header("action_dispatch.show_detailed_exceptions")
64
+ begin
65
+ content_type = request.formats.first
66
+ rescue Mime::Type::InvalidMimeType
67
+ render_for_api_request(Mime[:text], wrapper)
68
+ end
69
+
70
+ if api_request?(content_type)
71
+ render_for_api_request(content_type, wrapper)
72
+ else
73
+ render_for_browser_request(request, wrapper)
74
+ end
75
+ else
76
+ raise exception
77
+ end
78
+ end
35
79
 
36
- if env['action_dispatch.show_detailed_exceptions']
37
- request = Request.new(env)
38
- traces = wrapper.traces
80
+ def render_for_browser_request(request, wrapper)
81
+ template = create_template(request, wrapper)
82
+ file = "rescues/#{wrapper.rescue_template}"
39
83
 
40
- trace_to_show = 'Application Trace'
41
- if traces[trace_to_show].empty? && wrapper.rescue_template != 'routing_error'
42
- trace_to_show = 'Full Trace'
84
+ if request.xhr?
85
+ body = template.render(template: file, layout: false, formats: [:text])
86
+ format = "text/plain"
87
+ else
88
+ body = template.render(template: file, layout: "rescues/layout")
89
+ format = "text/html"
43
90
  end
91
+ render(wrapper.status_code, body, format)
92
+ end
44
93
 
45
- if source_to_show = traces[trace_to_show].first
46
- source_to_show_id = source_to_show[:id]
94
+ def render_for_api_request(content_type, wrapper)
95
+ body = {
96
+ status: wrapper.status_code,
97
+ error: Rack::Utils::HTTP_STATUS_CODES.fetch(
98
+ wrapper.status_code,
99
+ Rack::Utils::HTTP_STATUS_CODES[500]
100
+ ),
101
+ exception: wrapper.exception.inspect,
102
+ traces: wrapper.traces
103
+ }
104
+
105
+ to_format = "to_#{content_type.to_sym}"
106
+
107
+ if content_type && body.respond_to?(to_format)
108
+ formatted_body = body.public_send(to_format)
109
+ format = content_type
110
+ else
111
+ formatted_body = body.to_json
112
+ format = Mime[:json]
47
113
  end
48
114
 
49
- template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
115
+ render(wrapper.status_code, formatted_body, format)
116
+ end
117
+
118
+ def create_template(request, wrapper)
119
+ DebugView.new(
50
120
  request: request,
121
+ exception_wrapper: wrapper,
51
122
  exception: wrapper.exception,
52
- traces: traces,
53
- show_source_idx: source_to_show_id,
54
- trace_to_show: trace_to_show,
55
- routes_inspector: routes_inspector(exception),
123
+ traces: wrapper.traces,
124
+ show_source_idx: wrapper.source_to_show_id,
125
+ trace_to_show: wrapper.trace_to_show,
126
+ routes_inspector: routes_inspector(wrapper.exception),
56
127
  source_extracts: wrapper.source_extracts,
57
128
  line_number: wrapper.line_number,
58
129
  file: wrapper.file
59
130
  )
60
- file = "rescues/#{wrapper.rescue_template}"
131
+ end
61
132
 
62
- if request.xhr?
63
- body = template.render(template: file, layout: false, formats: [:text])
64
- format = "text/plain"
65
- else
66
- body = template.render(template: file, layout: 'rescues/layout')
67
- format = "text/html"
68
- end
69
- render(wrapper.status_code, body, format)
70
- else
71
- raise exception
133
+ def render(status, body, format)
134
+ [status, { "Content-Type" => "#{format}; charset=#{Response.default_charset}", "Content-Length" => body.bytesize.to_s }, [body]]
72
135
  end
73
- end
74
136
 
75
- def render(status, body, format)
76
- [status, {'Content-Type' => "#{format}; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
77
- end
137
+ def log_error(request, wrapper)
138
+ logger = logger(request)
139
+
140
+ return unless logger
78
141
 
79
- def log_error(env, wrapper)
80
- logger = logger(env)
81
- return unless logger
142
+ exception = wrapper.exception
82
143
 
83
- exception = wrapper.exception
144
+ trace = wrapper.application_trace
145
+ trace = wrapper.framework_trace if trace.empty?
84
146
 
85
- trace = wrapper.application_trace
86
- trace = wrapper.framework_trace if trace.empty?
147
+ ActiveSupport::Deprecation.silence do
148
+ message = []
149
+ message << " "
150
+ message << "#{exception.class} (#{exception.message}):"
151
+ message.concat(exception.annotated_source_code) if exception.respond_to?(:annotated_source_code)
152
+ message << " "
153
+ message.concat(trace)
87
154
 
88
- ActiveSupport::Deprecation.silence do
89
- message = "\n#{exception.class} (#{exception.message}):\n"
90
- message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
91
- message << " " << trace.join("\n ")
92
- logger.fatal("#{message}\n\n")
155
+ log_array(logger, message)
156
+ end
93
157
  end
94
- end
95
158
 
96
- def logger(env)
97
- env['action_dispatch.logger'] || stderr_logger
98
- end
159
+ def log_array(logger, array)
160
+ lines = Array(array)
99
161
 
100
- def stderr_logger
101
- @stderr_logger ||= ActiveSupport::Logger.new($stderr)
102
- end
162
+ return if lines.empty?
103
163
 
104
- def routes_inspector(exception)
105
- if @routes_app.respond_to?(:routes) && (exception.is_a?(ActionController::RoutingError) || exception.is_a?(ActionView::Template::Error))
106
- ActionDispatch::Routing::RoutesInspector.new(@routes_app.routes.routes)
164
+ if logger.formatter && logger.formatter.respond_to?(:tags_text)
165
+ logger.fatal lines.join("\n#{logger.formatter.tags_text}")
166
+ else
167
+ logger.fatal lines.join("\n")
168
+ end
169
+ end
170
+
171
+ def logger(request)
172
+ request.logger || ActionView::Base.logger || stderr_logger
173
+ end
174
+
175
+ def stderr_logger
176
+ @stderr_logger ||= ActiveSupport::Logger.new($stderr)
177
+ end
178
+
179
+ def routes_inspector(exception)
180
+ if @routes_app.respond_to?(:routes) && (exception.is_a?(ActionController::RoutingError) || exception.is_a?(ActionView::Template::Error))
181
+ ActionDispatch::Routing::RoutesInspector.new(@routes_app.routes.routes)
182
+ end
183
+ end
184
+
185
+ def api_request?(content_type)
186
+ @response_format == :api && !content_type.html?
107
187
  end
108
- end
109
188
  end
110
189
  end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionDispatch
4
+ # This middleware can be used to diagnose deadlocks in the autoload interlock.
5
+ #
6
+ # To use it, insert it near the top of the middleware stack, using
7
+ # <tt>config/application.rb</tt>:
8
+ #
9
+ # config.middleware.insert_before Rack::Sendfile, ActionDispatch::DebugLocks
10
+ #
11
+ # After restarting the application and re-triggering the deadlock condition,
12
+ # <tt>/rails/locks</tt> will show a summary of all threads currently known to
13
+ # the interlock, which lock level they are holding or awaiting, and their
14
+ # current backtrace.
15
+ #
16
+ # Generally a deadlock will be caused by the interlock conflicting with some
17
+ # other external lock or blocking I/O call. These cannot be automatically
18
+ # identified, but should be visible in the displayed backtraces.
19
+ #
20
+ # NOTE: The formatting and content of this middleware's output is intended for
21
+ # human consumption, and should be expected to change between releases.
22
+ #
23
+ # This middleware exposes operational details of the server, with no access
24
+ # control. It should only be enabled when in use, and removed thereafter.
25
+ class DebugLocks
26
+ def initialize(app, path = "/rails/locks")
27
+ @app = app
28
+ @path = path
29
+ end
30
+
31
+ def call(env)
32
+ req = ActionDispatch::Request.new env
33
+
34
+ if req.get?
35
+ path = req.path_info.chomp("/")
36
+ if path == @path
37
+ return render_details(req)
38
+ end
39
+ end
40
+
41
+ @app.call(env)
42
+ end
43
+
44
+ private
45
+ def render_details(req)
46
+ threads = ActiveSupport::Dependencies.interlock.raw_state do |raw_threads|
47
+ # The Interlock itself comes to a complete halt as long as this block
48
+ # is executing. That gives us a more consistent picture of everything,
49
+ # but creates a pretty strong Observer Effect.
50
+ #
51
+ # Most directly, that means we need to do as little as possible in
52
+ # this block. More widely, it means this middleware should remain a
53
+ # strictly diagnostic tool (to be used when something has gone wrong),
54
+ # and not for any sort of general monitoring.
55
+
56
+ raw_threads.each.with_index do |(thread, info), idx|
57
+ info[:index] = idx
58
+ info[:backtrace] = thread.backtrace
59
+ end
60
+
61
+ raw_threads
62
+ end
63
+
64
+ str = threads.map do |thread, info|
65
+ if info[:exclusive]
66
+ lock_state = +"Exclusive"
67
+ elsif info[:sharing] > 0
68
+ lock_state = +"Sharing"
69
+ lock_state << " x#{info[:sharing]}" if info[:sharing] > 1
70
+ else
71
+ lock_state = +"No lock"
72
+ end
73
+
74
+ if info[:waiting]
75
+ lock_state << " (yielded share)"
76
+ end
77
+
78
+ msg = +"Thread #{info[:index]} [0x#{thread.__id__.to_s(16)} #{thread.status || 'dead'}] #{lock_state}\n"
79
+
80
+ if info[:sleeper]
81
+ msg << " Waiting in #{info[:sleeper]}"
82
+ msg << " to #{info[:purpose].to_s.inspect}" unless info[:purpose].nil?
83
+ msg << "\n"
84
+
85
+ if info[:compatible]
86
+ compat = info[:compatible].map { |c| c == false ? "share" : c.to_s.inspect }
87
+ msg << " may be pre-empted for: #{compat.join(', ')}\n"
88
+ end
89
+
90
+ blockers = threads.values.select { |binfo| blocked_by?(info, binfo, threads.values) }
91
+ msg << " blocked by: #{blockers.map { |i| i[:index] }.join(', ')}\n" if blockers.any?
92
+ end
93
+
94
+ blockees = threads.values.select { |binfo| blocked_by?(binfo, info, threads.values) }
95
+ msg << " blocking: #{blockees.map { |i| i[:index] }.join(', ')}\n" if blockees.any?
96
+
97
+ msg << "\n#{info[:backtrace].join("\n")}\n" if info[:backtrace]
98
+ end.join("\n\n---\n\n\n")
99
+
100
+ [200, { "Content-Type" => "text/plain", "Content-Length" => str.size }, [str]]
101
+ end
102
+
103
+ def blocked_by?(victim, blocker, all_threads)
104
+ return false if victim.equal?(blocker)
105
+
106
+ case victim[:sleeper]
107
+ when :start_sharing
108
+ blocker[:exclusive] ||
109
+ (!victim[:waiting] && blocker[:compatible] && !blocker[:compatible].include?(false))
110
+ when :start_exclusive
111
+ blocker[:sharing] > 0 ||
112
+ blocker[:exclusive] ||
113
+ (blocker[:compatible] && !blocker[:compatible].include?(victim[:purpose]))
114
+ when :yield_shares
115
+ blocker[:exclusive]
116
+ when :stop_exclusive
117
+ blocker[:exclusive] ||
118
+ victim[:compatible] &&
119
+ victim[:compatible].include?(blocker[:purpose]) &&
120
+ all_threads.all? { |other| !other[:compatible] || blocker.equal?(other) || other[:compatible].include?(blocker[:purpose]) }
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pp"
4
+
5
+ require "action_view"
6
+ require "action_view/base"
7
+
8
+ module ActionDispatch
9
+ class DebugView < ActionView::Base # :nodoc:
10
+ RESCUES_TEMPLATE_PATH = File.expand_path("templates", __dir__)
11
+
12
+ def initialize(assigns)
13
+ paths = [RESCUES_TEMPLATE_PATH]
14
+ lookup_context = ActionView::LookupContext.new(paths)
15
+ super(lookup_context, assigns)
16
+ end
17
+
18
+ def compiled_method_container
19
+ self.class
20
+ end
21
+
22
+ def debug_params(params)
23
+ clean_params = params.clone
24
+ clean_params.delete("action")
25
+ clean_params.delete("controller")
26
+
27
+ if clean_params.empty?
28
+ "None"
29
+ else
30
+ PP.pp(clean_params, +"", 200)
31
+ end
32
+ end
33
+
34
+ def debug_headers(headers)
35
+ if headers.present?
36
+ headers.inspect.gsub(",", ",\n")
37
+ else
38
+ "None"
39
+ end
40
+ end
41
+
42
+ def debug_hash(object)
43
+ object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
44
+ end
45
+
46
+ def render(*)
47
+ logger = ActionView::Base.logger
48
+
49
+ if logger && logger.respond_to?(:silence)
50
+ logger.silence { super }
51
+ else
52
+ super
53
+ end
54
+ end
55
+
56
+ def protect_against_forgery?
57
+ false
58
+ end
59
+
60
+ def params_valid?
61
+ @request.parameters
62
+ rescue ActionController::BadRequest
63
+ false
64
+ end
65
+ end
66
+ end
@@ -1,40 +1,57 @@
1
- require 'action_controller/metal/exceptions'
2
- require 'active_support/core_ext/module/attribute_accessors'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/module/attribute_accessors"
4
+ require "rack/utils"
3
5
 
4
6
  module ActionDispatch
5
7
  class ExceptionWrapper
6
- cattr_accessor :rescue_responses
7
- @@rescue_responses = Hash.new(:internal_server_error)
8
- @@rescue_responses.merge!(
9
- 'ActionController::RoutingError' => :not_found,
10
- 'AbstractController::ActionNotFound' => :not_found,
11
- 'ActionController::MethodNotAllowed' => :method_not_allowed,
12
- 'ActionController::UnknownHttpMethod' => :method_not_allowed,
13
- 'ActionController::NotImplemented' => :not_implemented,
14
- 'ActionController::UnknownFormat' => :not_acceptable,
15
- 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity,
16
- 'ActionController::InvalidCrossOriginRequest' => :unprocessable_entity,
17
- 'ActionDispatch::ParamsParser::ParseError' => :bad_request,
18
- 'ActionController::BadRequest' => :bad_request,
19
- 'ActionController::ParameterMissing' => :bad_request
8
+ cattr_accessor :rescue_responses, default: Hash.new(:internal_server_error).merge!(
9
+ "ActionController::RoutingError" => :not_found,
10
+ "AbstractController::ActionNotFound" => :not_found,
11
+ "ActionController::MethodNotAllowed" => :method_not_allowed,
12
+ "ActionController::UnknownHttpMethod" => :method_not_allowed,
13
+ "ActionController::NotImplemented" => :not_implemented,
14
+ "ActionController::UnknownFormat" => :not_acceptable,
15
+ "Mime::Type::InvalidMimeType" => :not_acceptable,
16
+ "ActionController::MissingExactTemplate" => :not_acceptable,
17
+ "ActionController::InvalidAuthenticityToken" => :unprocessable_entity,
18
+ "ActionController::InvalidCrossOriginRequest" => :unprocessable_entity,
19
+ "ActionDispatch::Http::Parameters::ParseError" => :bad_request,
20
+ "ActionController::BadRequest" => :bad_request,
21
+ "ActionController::ParameterMissing" => :bad_request,
22
+ "Rack::QueryParser::ParameterTypeError" => :bad_request,
23
+ "Rack::QueryParser::InvalidParameterError" => :bad_request
20
24
  )
21
25
 
22
- cattr_accessor :rescue_templates
23
- @@rescue_templates = Hash.new('diagnostics')
24
- @@rescue_templates.merge!(
25
- 'ActionView::MissingTemplate' => 'missing_template',
26
- 'ActionController::RoutingError' => 'routing_error',
27
- 'AbstractController::ActionNotFound' => 'unknown_action',
28
- 'ActionView::Template::Error' => 'template_error'
26
+ cattr_accessor :rescue_templates, default: Hash.new("diagnostics").merge!(
27
+ "ActionView::MissingTemplate" => "missing_template",
28
+ "ActionController::RoutingError" => "routing_error",
29
+ "AbstractController::ActionNotFound" => "unknown_action",
30
+ "ActiveRecord::StatementInvalid" => "invalid_statement",
31
+ "ActionView::Template::Error" => "template_error",
32
+ "ActionController::MissingExactTemplate" => "missing_exact_template",
29
33
  )
30
34
 
31
- attr_reader :env, :exception, :line_number, :file
35
+ cattr_accessor :wrapper_exceptions, default: [
36
+ "ActionView::Template::Error"
37
+ ]
38
+
39
+ attr_reader :backtrace_cleaner, :exception, :wrapped_causes, :line_number, :file
32
40
 
33
- def initialize(env, exception)
34
- @env = env
35
- @exception = original_exception(exception)
41
+ def initialize(backtrace_cleaner, exception)
42
+ @backtrace_cleaner = backtrace_cleaner
43
+ @exception = exception
44
+ @wrapped_causes = wrapped_causes_for(exception, backtrace_cleaner)
45
+
46
+ expand_backtrace if exception.is_a?(SyntaxError) || exception.cause.is_a?(SyntaxError)
47
+ end
36
48
 
37
- expand_backtrace if exception.is_a?(SyntaxError) || exception.try(:original_exception).try(:is_a?, SyntaxError)
49
+ def unwrapped_exception
50
+ if wrapper_exceptions.include?(exception.class.to_s)
51
+ exception.cause
52
+ else
53
+ exception
54
+ end
38
55
  end
39
56
 
40
57
  def rescue_template
@@ -42,7 +59,7 @@ module ActionDispatch
42
59
  end
43
60
 
44
61
  def status_code
45
- self.class.status_code_for_exception(@exception.class.name)
62
+ self.class.status_code_for_exception(unwrapped_exception.class.name)
46
63
  end
47
64
 
48
65
  def application_trace
@@ -58,15 +75,19 @@ module ActionDispatch
58
75
  end
59
76
 
60
77
  def traces
61
- appplication_trace_with_ids = []
78
+ application_trace_with_ids = []
62
79
  framework_trace_with_ids = []
63
80
  full_trace_with_ids = []
64
81
 
65
82
  full_trace.each_with_index do |trace, idx|
66
- trace_with_id = { id: idx, trace: trace }
83
+ trace_with_id = {
84
+ exception_object_id: @exception.object_id,
85
+ id: idx,
86
+ trace: trace
87
+ }
67
88
 
68
89
  if application_trace.include?(trace)
69
- appplication_trace_with_ids << trace_with_id
90
+ application_trace_with_ids << trace_with_id
70
91
  else
71
92
  framework_trace_with_ids << trace_with_id
72
93
  end
@@ -75,7 +96,7 @@ module ActionDispatch
75
96
  end
76
97
 
77
98
  {
78
- "Application Trace" => appplication_trace_with_ids,
99
+ "Application Trace" => application_trace_with_ids,
79
100
  "Framework Trace" => framework_trace_with_ids,
80
101
  "Full Trace" => full_trace_with_ids
81
102
  }
@@ -87,8 +108,7 @@ module ActionDispatch
87
108
 
88
109
  def source_extracts
89
110
  backtrace.map do |trace|
90
- file, line = trace.split(":")
91
- line_number = line.to_i
111
+ file, line_number = extract_file_and_line_number(trace)
92
112
 
93
113
  {
94
114
  code: source_fragment(file, line_number),
@@ -97,52 +117,64 @@ module ActionDispatch
97
117
  end
98
118
  end
99
119
 
100
- private
101
-
102
- def backtrace
103
- Array(@exception.backtrace)
104
- end
105
-
106
- def original_exception(exception)
107
- if registered_original_exception?(exception)
108
- exception.original_exception
120
+ def trace_to_show
121
+ if traces["Application Trace"].empty? && rescue_template != "routing_error"
122
+ "Full Trace"
109
123
  else
110
- exception
124
+ "Application Trace"
111
125
  end
112
126
  end
113
127
 
114
- def registered_original_exception?(exception)
115
- exception.respond_to?(:original_exception) && @@rescue_responses.has_key?(exception.original_exception.class.name)
128
+ def source_to_show_id
129
+ (traces[trace_to_show].first || {})[:id]
116
130
  end
117
131
 
118
- def clean_backtrace(*args)
119
- if backtrace_cleaner
120
- backtrace_cleaner.clean(backtrace, *args)
121
- else
122
- backtrace
132
+ private
133
+ def backtrace
134
+ Array(@exception.backtrace)
123
135
  end
124
- end
125
136
 
126
- def backtrace_cleaner
127
- @backtrace_cleaner ||= @env['action_dispatch.backtrace_cleaner']
128
- end
137
+ def causes_for(exception)
138
+ return enum_for(__method__, exception) unless block_given?
139
+
140
+ yield exception while exception = exception.cause
141
+ end
129
142
 
130
- def source_fragment(path, line)
131
- return unless Rails.respond_to?(:root) && Rails.root
132
- full_path = Rails.root.join(path)
133
- if File.exist?(full_path)
134
- File.open(full_path, "r") do |file|
135
- start = [line - 3, 0].max
136
- lines = file.each_line.drop(start).take(6)
137
- Hash[*(start+1..(lines.count+start)).zip(lines).flatten]
143
+ def wrapped_causes_for(exception, backtrace_cleaner)
144
+ causes_for(exception).map { |cause| self.class.new(backtrace_cleaner, cause) }
145
+ end
146
+
147
+ def clean_backtrace(*args)
148
+ if backtrace_cleaner
149
+ backtrace_cleaner.clean(backtrace, *args)
150
+ else
151
+ backtrace
138
152
  end
139
153
  end
140
- end
141
154
 
142
- def expand_backtrace
143
- @exception.backtrace.unshift(
144
- @exception.to_s.split("\n")
145
- ).flatten!
146
- end
155
+ def source_fragment(path, line)
156
+ return unless Rails.respond_to?(:root) && Rails.root
157
+ full_path = Rails.root.join(path)
158
+ if File.exist?(full_path)
159
+ File.open(full_path, "r") do |file|
160
+ start = [line - 3, 0].max
161
+ lines = file.each_line.drop(start).take(6)
162
+ Hash[*(start + 1..(lines.count + start)).zip(lines).flatten]
163
+ end
164
+ end
165
+ end
166
+
167
+ def extract_file_and_line_number(trace)
168
+ # Split by the first colon followed by some digits, which works for both
169
+ # Windows and Unix path styles.
170
+ file, line = trace.match(/^(.+?):(\d+).*$/, &:captures) || trace
171
+ [file, line.to_i]
172
+ end
173
+
174
+ def expand_backtrace
175
+ @exception.backtrace.unshift(
176
+ @exception.to_s.split("\n")
177
+ ).flatten!
178
+ end
147
179
  end
148
180
  end