actionpack 5.2.1 → 7.0.2.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.

Files changed (167) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +264 -220
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -6
  5. data/lib/abstract_controller/asset_paths.rb +1 -1
  6. data/lib/abstract_controller/base.rb +24 -4
  7. data/lib/abstract_controller/caching/fragments.rb +8 -24
  8. data/lib/abstract_controller/caching.rb +2 -2
  9. data/lib/abstract_controller/callbacks.rb +34 -8
  10. data/lib/abstract_controller/collector.rb +5 -4
  11. data/lib/abstract_controller/error.rb +1 -1
  12. data/lib/abstract_controller/helpers.rb +107 -90
  13. data/lib/abstract_controller/logger.rb +1 -1
  14. data/lib/abstract_controller/railties/routes_helpers.rb +19 -1
  15. data/lib/abstract_controller/rendering.rb +9 -9
  16. data/lib/abstract_controller/translation.rb +12 -5
  17. data/lib/abstract_controller/url_for.rb +4 -6
  18. data/lib/abstract_controller.rb +2 -0
  19. data/lib/action_controller/api.rb +5 -4
  20. data/lib/action_controller/base.rb +6 -9
  21. data/lib/action_controller/caching.rb +1 -3
  22. data/lib/action_controller/log_subscriber.rb +13 -9
  23. data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
  24. data/lib/action_controller/metal/conditional_get.rb +57 -6
  25. data/lib/action_controller/metal/content_security_policy.rb +2 -3
  26. data/lib/action_controller/metal/cookies.rb +4 -2
  27. data/lib/action_controller/metal/data_streaming.rb +9 -18
  28. data/lib/action_controller/metal/default_headers.rb +17 -0
  29. data/lib/action_controller/metal/etag_with_template_digest.rb +4 -6
  30. data/lib/action_controller/metal/exceptions.rb +55 -12
  31. data/lib/action_controller/metal/flash.rb +10 -6
  32. data/lib/action_controller/metal/head.rb +7 -4
  33. data/lib/action_controller/metal/helpers.rb +15 -6
  34. data/lib/action_controller/metal/http_authentication.rb +41 -39
  35. data/lib/action_controller/metal/implicit_render.rb +5 -15
  36. data/lib/action_controller/metal/instrumentation.rb +59 -55
  37. data/lib/action_controller/metal/live.rb +80 -33
  38. data/lib/action_controller/metal/logging.rb +20 -0
  39. data/lib/action_controller/metal/mime_responds.rb +22 -7
  40. data/lib/action_controller/metal/parameter_encoding.rb +35 -4
  41. data/lib/action_controller/metal/params_wrapper.rb +50 -31
  42. data/lib/action_controller/metal/permissions_policy.rb +46 -0
  43. data/lib/action_controller/metal/redirecting.rb +93 -23
  44. data/lib/action_controller/metal/renderers.rb +4 -4
  45. data/lib/action_controller/metal/rendering.rb +14 -9
  46. data/lib/action_controller/metal/request_forgery_protection.rb +160 -58
  47. data/lib/action_controller/metal/rescue.rb +2 -2
  48. data/lib/action_controller/metal/streaming.rb +1 -4
  49. data/lib/action_controller/metal/strong_parameters.rb +236 -88
  50. data/lib/action_controller/metal/testing.rb +9 -2
  51. data/lib/action_controller/metal/url_for.rb +1 -1
  52. data/lib/action_controller/metal.rb +16 -17
  53. data/lib/action_controller/railtie.rb +49 -6
  54. data/lib/action_controller/railties/helpers.rb +1 -1
  55. data/lib/action_controller/renderer.rb +37 -13
  56. data/lib/action_controller/template_assertions.rb +1 -1
  57. data/lib/action_controller/test_case.rb +98 -68
  58. data/lib/action_controller.rb +4 -5
  59. data/lib/action_dispatch/http/cache.rb +45 -32
  60. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  61. data/lib/action_dispatch/http/content_security_policy.rb +69 -56
  62. data/lib/action_dispatch/http/filter_parameters.rb +14 -8
  63. data/lib/action_dispatch/http/filter_redirect.rb +2 -3
  64. data/lib/action_dispatch/http/headers.rb +4 -4
  65. data/lib/action_dispatch/http/mime_negotiation.rb +44 -16
  66. data/lib/action_dispatch/http/mime_type.rb +47 -30
  67. data/lib/action_dispatch/http/parameters.rb +18 -27
  68. data/lib/action_dispatch/http/permissions_policy.rb +173 -0
  69. data/lib/action_dispatch/http/request.rb +49 -35
  70. data/lib/action_dispatch/http/response.rb +34 -26
  71. data/lib/action_dispatch/http/upload.rb +9 -1
  72. data/lib/action_dispatch/http/url.rb +86 -94
  73. data/lib/action_dispatch/journey/formatter.rb +55 -31
  74. data/lib/action_dispatch/journey/gtg/builder.rb +30 -46
  75. data/lib/action_dispatch/journey/gtg/simulator.rb +15 -8
  76. data/lib/action_dispatch/journey/gtg/transition_table.rb +78 -21
  77. data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
  78. data/lib/action_dispatch/journey/nodes/node.rb +83 -16
  79. data/lib/action_dispatch/journey/parser.rb +13 -13
  80. data/lib/action_dispatch/journey/parser.y +1 -1
  81. data/lib/action_dispatch/journey/path/pattern.rb +42 -34
  82. data/lib/action_dispatch/journey/route.rb +14 -31
  83. data/lib/action_dispatch/journey/router/utils.rb +16 -14
  84. data/lib/action_dispatch/journey/router.rb +27 -35
  85. data/lib/action_dispatch/journey/routes.rb +3 -5
  86. data/lib/action_dispatch/journey/scanner.rb +10 -4
  87. data/lib/action_dispatch/journey/visitors.rb +1 -4
  88. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  89. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  90. data/lib/action_dispatch/journey.rb +0 -2
  91. data/lib/action_dispatch/middleware/actionable_exceptions.rb +45 -0
  92. data/lib/action_dispatch/middleware/callbacks.rb +2 -4
  93. data/lib/action_dispatch/middleware/cookies.rb +136 -113
  94. data/lib/action_dispatch/middleware/debug_exceptions.rb +47 -68
  95. data/lib/action_dispatch/middleware/debug_locks.rb +8 -8
  96. data/lib/action_dispatch/middleware/debug_view.rb +66 -0
  97. data/lib/action_dispatch/middleware/exception_wrapper.rb +79 -30
  98. data/lib/action_dispatch/middleware/executor.rb +4 -1
  99. data/lib/action_dispatch/middleware/flash.rb +10 -12
  100. data/lib/action_dispatch/middleware/host_authorization.rb +159 -0
  101. data/lib/action_dispatch/middleware/public_exceptions.rb +6 -3
  102. data/lib/action_dispatch/middleware/remote_ip.rb +30 -20
  103. data/lib/action_dispatch/middleware/request_id.rb +5 -6
  104. data/lib/action_dispatch/middleware/server_timing.rb +33 -0
  105. data/lib/action_dispatch/middleware/session/abstract_store.rb +16 -3
  106. data/lib/action_dispatch/middleware/session/cache_store.rb +11 -6
  107. data/lib/action_dispatch/middleware/session/cookie_store.rb +24 -19
  108. data/lib/action_dispatch/middleware/show_exceptions.rb +20 -11
  109. data/lib/action_dispatch/middleware/ssl.rb +20 -15
  110. data/lib/action_dispatch/middleware/stack.rb +79 -7
  111. data/lib/action_dispatch/middleware/static.rb +150 -94
  112. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  113. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  114. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  115. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +6 -11
  116. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  117. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +4 -2
  118. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +46 -36
  119. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +8 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +7 -0
  121. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +25 -6
  122. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
  123. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +9 -6
  124. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +4 -1
  125. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +121 -15
  126. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  128. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +5 -5
  129. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +4 -4
  130. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +5 -5
  131. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
  132. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +16 -2
  133. data/lib/action_dispatch/railtie.rb +16 -4
  134. data/lib/action_dispatch/request/session.rb +59 -22
  135. data/lib/action_dispatch/request/utils.rb +28 -2
  136. data/lib/action_dispatch/routing/inspector.rb +102 -54
  137. data/lib/action_dispatch/routing/mapper.rb +184 -156
  138. data/lib/action_dispatch/routing/polymorphic_routes.rb +21 -19
  139. data/lib/action_dispatch/routing/redirection.rb +4 -6
  140. data/lib/action_dispatch/routing/route_set.rb +83 -73
  141. data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
  142. data/lib/action_dispatch/routing/url_for.rb +2 -3
  143. data/lib/action_dispatch/routing.rb +23 -22
  144. data/lib/action_dispatch/system_test_case.rb +65 -16
  145. data/lib/action_dispatch/system_testing/browser.rb +43 -16
  146. data/lib/action_dispatch/system_testing/driver.rb +42 -10
  147. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +58 -12
  148. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +3 -10
  149. data/lib/action_dispatch/testing/assertion_response.rb +0 -1
  150. data/lib/action_dispatch/testing/assertions/response.rb +4 -7
  151. data/lib/action_dispatch/testing/assertions/routing.rb +20 -8
  152. data/lib/action_dispatch/testing/assertions.rb +3 -6
  153. data/lib/action_dispatch/testing/integration.rb +61 -30
  154. data/lib/action_dispatch/testing/request_encoder.rb +2 -2
  155. data/lib/action_dispatch/testing/test_process.rb +8 -6
  156. data/lib/action_dispatch/testing/test_request.rb +3 -3
  157. data/lib/action_dispatch/testing/test_response.rb +4 -32
  158. data/lib/action_dispatch.rb +15 -7
  159. data/lib/action_pack/gem_version.rb +4 -4
  160. data/lib/action_pack.rb +1 -1
  161. metadata +44 -25
  162. data/lib/action_controller/metal/force_ssl.rb +0 -99
  163. data/lib/action_dispatch/http/parameter_filter.rb +0 -86
  164. data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
  165. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -49
  166. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -120
  167. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +0 -26
@@ -1,59 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "action_dispatch/http/request"
4
3
  require "action_dispatch/middleware/exception_wrapper"
5
4
  require "action_dispatch/routing/inspector"
6
- require "action_view"
7
- require "action_view/base"
8
5
 
9
- require "pp"
6
+ require "action_view"
10
7
 
11
8
  module ActionDispatch
12
9
  # This middleware is responsible for logging exceptions and
13
10
  # showing a debugging page in case the request is local.
14
11
  class DebugExceptions
15
- RESCUES_TEMPLATE_PATH = File.expand_path("templates", __dir__)
16
-
17
- class DebugView < ActionView::Base
18
- def debug_params(params)
19
- clean_params = params.clone
20
- clean_params.delete("action")
21
- clean_params.delete("controller")
12
+ cattr_reader :interceptors, instance_accessor: false, default: []
22
13
 
23
- if clean_params.empty?
24
- "None"
25
- else
26
- PP.pp(clean_params, "".dup, 200)
27
- end
28
- end
29
-
30
- def debug_headers(headers)
31
- if headers.present?
32
- headers.inspect.gsub(",", ",\n")
33
- else
34
- "None"
35
- end
36
- end
37
-
38
- def debug_hash(object)
39
- object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
40
- end
41
-
42
- def render(*)
43
- logger = ActionView::Base.logger
44
-
45
- if logger && logger.respond_to?(:silence)
46
- logger.silence { super }
47
- else
48
- super
49
- end
50
- end
14
+ def self.register_interceptor(object = nil, &block)
15
+ interceptor = object || block
16
+ interceptors << interceptor
51
17
  end
52
18
 
53
- def initialize(app, routes_app = nil, response_format = :default)
19
+ def initialize(app, routes_app = nil, response_format = :default, interceptors = self.class.interceptors)
54
20
  @app = app
55
21
  @routes_app = routes_app
56
22
  @response_format = response_format
23
+ @interceptors = interceptors
57
24
  end
58
25
 
59
26
  def call(env)
@@ -67,11 +34,22 @@ module ActionDispatch
67
34
 
68
35
  response
69
36
  rescue Exception => exception
37
+ invoke_interceptors(request, exception)
70
38
  raise exception unless request.show_exceptions?
71
39
  render_exception(request, exception)
72
40
  end
73
41
 
74
42
  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
+
47
+ @interceptors.each do |interceptor|
48
+ interceptor.call(request, exception)
49
+ rescue Exception
50
+ log_error(request, wrapper)
51
+ end
52
+ end
75
53
 
76
54
  def render_exception(request, exception)
77
55
  backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
@@ -79,7 +57,11 @@ module ActionDispatch
79
57
  log_error(request, wrapper)
80
58
 
81
59
  if request.get_header("action_dispatch.show_detailed_exceptions")
82
- content_type = request.formats.first
60
+ begin
61
+ content_type = request.formats.first
62
+ rescue ActionDispatch::Http::MimeNegotiation::InvalidType
63
+ content_type = Mime[:text]
64
+ end
83
65
 
84
66
  if api_request?(content_type)
85
67
  render_for_api_request(content_type, wrapper)
@@ -130,23 +112,13 @@ module ActionDispatch
130
112
  end
131
113
 
132
114
  def create_template(request, wrapper)
133
- traces = wrapper.traces
134
-
135
- trace_to_show = "Application Trace"
136
- if traces[trace_to_show].empty? && wrapper.rescue_template != "routing_error"
137
- trace_to_show = "Full Trace"
138
- end
139
-
140
- if source_to_show = traces[trace_to_show].first
141
- source_to_show_id = source_to_show[:id]
142
- end
143
-
144
- DebugView.new([RESCUES_TEMPLATE_PATH],
115
+ DebugView.new(
145
116
  request: request,
117
+ exception_wrapper: wrapper,
146
118
  exception: wrapper.exception,
147
- traces: traces,
148
- show_source_idx: source_to_show_id,
149
- trace_to_show: trace_to_show,
119
+ traces: wrapper.traces,
120
+ show_source_idx: wrapper.source_to_show_id,
121
+ trace_to_show: wrapper.trace_to_show,
150
122
  routes_inspector: routes_inspector(wrapper.exception),
151
123
  source_extracts: wrapper.source_extracts,
152
124
  line_number: wrapper.line_number,
@@ -160,27 +132,30 @@ module ActionDispatch
160
132
 
161
133
  def log_error(request, wrapper)
162
134
  logger = logger(request)
135
+
163
136
  return unless logger
137
+ return if !log_rescued_responses?(request) && wrapper.rescue_response?
164
138
 
165
139
  exception = wrapper.exception
140
+ trace = wrapper.exception_trace
166
141
 
167
- trace = wrapper.application_trace
168
- trace = wrapper.framework_trace if trace.empty?
142
+ message = []
143
+ message << " "
144
+ message << "#{exception.class} (#{exception.message}):"
145
+ message.concat(exception.annotated_source_code) if exception.respond_to?(:annotated_source_code)
146
+ message << " "
147
+ message.concat(trace)
169
148
 
170
- ActiveSupport::Deprecation.silence do
171
- logger.fatal " "
172
- logger.fatal "#{exception.class} (#{exception.message}):"
173
- log_array logger, exception.annoted_source_code if exception.respond_to?(:annoted_source_code)
174
- logger.fatal " "
175
- log_array logger, trace
176
- end
149
+ log_array(logger, message)
177
150
  end
178
151
 
179
- def log_array(logger, array)
152
+ def log_array(logger, lines)
153
+ return if lines.empty?
154
+
180
155
  if logger.formatter && logger.formatter.respond_to?(:tags_text)
181
- logger.fatal array.join("\n#{logger.formatter.tags_text}")
156
+ logger.fatal lines.join("\n#{logger.formatter.tags_text}")
182
157
  else
183
- logger.fatal array.join("\n")
158
+ logger.fatal lines.join("\n")
184
159
  end
185
160
  end
186
161
 
@@ -201,5 +176,9 @@ module ActionDispatch
201
176
  def api_request?(content_type)
202
177
  @response_format == :api && !content_type.html?
203
178
  end
179
+
180
+ def log_rescued_responses?(request)
181
+ request.get_header("action_dispatch.log_rescued_responses")
182
+ end
204
183
  end
205
184
  end
@@ -9,9 +9,9 @@ module ActionDispatch
9
9
  # config.middleware.insert_before Rack::Sendfile, ActionDispatch::DebugLocks
10
10
  #
11
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.
12
+ # the route <tt>/rails/locks</tt> will show a summary of all threads currently
13
+ # known to the interlock, which lock level they are holding or awaiting, and
14
+ # their current backtrace.
15
15
  #
16
16
  # Generally a deadlock will be caused by the interlock conflicting with some
17
17
  # other external lock or blocking I/O call. These cannot be automatically
@@ -32,7 +32,7 @@ module ActionDispatch
32
32
  req = ActionDispatch::Request.new env
33
33
 
34
34
  if req.get?
35
- path = req.path_info.chomp("/".freeze)
35
+ path = req.path_info.chomp("/")
36
36
  if path == @path
37
37
  return render_details(req)
38
38
  end
@@ -63,19 +63,19 @@ module ActionDispatch
63
63
 
64
64
  str = threads.map do |thread, info|
65
65
  if info[:exclusive]
66
- lock_state = "Exclusive".dup
66
+ lock_state = +"Exclusive"
67
67
  elsif info[:sharing] > 0
68
- lock_state = "Sharing".dup
68
+ lock_state = +"Sharing"
69
69
  lock_state << " x#{info[:sharing]}" if info[:sharing] > 1
70
70
  else
71
- lock_state = "No lock".dup
71
+ lock_state = +"No lock"
72
72
  end
73
73
 
74
74
  if info[:waiting]
75
75
  lock_state << " (yielded share)"
76
76
  end
77
77
 
78
- msg = "Thread #{info[:index]} [0x#{thread.__id__.to_s(16)} #{thread.status || 'dead'}] #{lock_state}\n".dup
78
+ msg = +"Thread #{info[:index]} [0x#{thread.__id__.to_s(16)} #{thread.status || 'dead'}] #{lock_state}\n"
79
79
 
80
80
  if info[:sleeper]
81
81
  msg << " Waiting in #{info[:sleeper]}"
@@ -0,0 +1,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, nil)
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
@@ -6,44 +6,72 @@ require "rack/utils"
6
6
  module ActionDispatch
7
7
  class ExceptionWrapper
8
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
- "ActionController::InvalidAuthenticityToken" => :unprocessable_entity,
16
- "ActionController::InvalidCrossOriginRequest" => :unprocessable_entity,
17
- "ActionDispatch::Http::Parameters::ParseError" => :bad_request,
18
- "ActionController::BadRequest" => :bad_request,
19
- "ActionController::ParameterMissing" => :bad_request,
20
- "Rack::QueryParser::ParameterTypeError" => :bad_request,
21
- "Rack::QueryParser::InvalidParameterError" => :bad_request
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
+ "ActionDispatch::Http::MimeNegotiation::InvalidType" => :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
22
24
  )
23
25
 
24
26
  cattr_accessor :rescue_templates, default: Hash.new("diagnostics").merge!(
25
- "ActionView::MissingTemplate" => "missing_template",
26
- "ActionController::RoutingError" => "routing_error",
27
- "AbstractController::ActionNotFound" => "unknown_action",
28
- "ActiveRecord::StatementInvalid" => "invalid_statement",
29
- "ActionView::Template::Error" => "template_error"
27
+ "ActionView::MissingTemplate" => "missing_template",
28
+ "ActionController::RoutingError" => "routing_error",
29
+ "AbstractController::ActionNotFound" => "unknown_action",
30
+ "ActiveRecord::StatementInvalid" => "invalid_statement",
31
+ "ActionView::Template::Error" => "template_error",
32
+ "ActionController::MissingExactTemplate" => "missing_exact_template",
30
33
  )
31
34
 
32
- attr_reader :backtrace_cleaner, :exception, :line_number, :file
35
+ cattr_accessor :wrapper_exceptions, default: [
36
+ "ActionView::Template::Error"
37
+ ]
38
+
39
+ cattr_accessor :silent_exceptions, default: [
40
+ "ActionController::RoutingError",
41
+ "ActionDispatch::Http::MimeNegotiation::InvalidType"
42
+ ]
43
+
44
+ attr_reader :backtrace_cleaner, :exception, :wrapped_causes, :line_number, :file
33
45
 
34
46
  def initialize(backtrace_cleaner, exception)
35
47
  @backtrace_cleaner = backtrace_cleaner
36
- @exception = original_exception(exception)
48
+ @exception = exception
49
+ @exception_class_name = @exception.class.name
50
+ @wrapped_causes = wrapped_causes_for(exception, backtrace_cleaner)
37
51
 
38
52
  expand_backtrace if exception.is_a?(SyntaxError) || exception.cause.is_a?(SyntaxError)
39
53
  end
40
54
 
55
+ def unwrapped_exception
56
+ if wrapper_exceptions.include?(@exception_class_name)
57
+ exception.cause
58
+ else
59
+ exception
60
+ end
61
+ end
62
+
41
63
  def rescue_template
42
- @@rescue_templates[@exception.class.name]
64
+ @@rescue_templates[@exception_class_name]
43
65
  end
44
66
 
45
67
  def status_code
46
- self.class.status_code_for_exception(@exception.class.name)
68
+ self.class.status_code_for_exception(unwrapped_exception.class.name)
69
+ end
70
+
71
+ def exception_trace
72
+ trace = application_trace
73
+ trace = framework_trace if trace.empty? && !silent_exceptions.include?(@exception_class_name)
74
+ trace
47
75
  end
48
76
 
49
77
  def application_trace
@@ -64,7 +92,11 @@ module ActionDispatch
64
92
  full_trace_with_ids = []
65
93
 
66
94
  full_trace.each_with_index do |trace, idx|
67
- trace_with_id = { id: idx, trace: trace }
95
+ trace_with_id = {
96
+ exception_object_id: @exception.object_id,
97
+ id: idx,
98
+ trace: trace
99
+ }
68
100
 
69
101
  if application_trace.include?(trace)
70
102
  application_trace_with_ids << trace_with_id
@@ -86,6 +118,10 @@ module ActionDispatch
86
118
  Rack::Utils.status_code(@@rescue_responses[class_name])
87
119
  end
88
120
 
121
+ def rescue_response?
122
+ @@rescue_responses.key?(exception.class.name)
123
+ end
124
+
89
125
  def source_extracts
90
126
  backtrace.map do |trace|
91
127
  file, line_number = extract_file_and_line_number(trace)
@@ -97,18 +133,31 @@ module ActionDispatch
97
133
  end
98
134
  end
99
135
 
100
- private
136
+ def trace_to_show
137
+ if traces["Application Trace"].empty? && rescue_template != "routing_error"
138
+ "Full Trace"
139
+ else
140
+ "Application Trace"
141
+ end
142
+ end
101
143
 
144
+ def source_to_show_id
145
+ (traces[trace_to_show].first || {})[:id]
146
+ end
147
+
148
+ private
102
149
  def backtrace
103
150
  Array(@exception.backtrace)
104
151
  end
105
152
 
106
- def original_exception(exception)
107
- if @@rescue_responses.has_key?(exception.cause.class.name)
108
- exception.cause
109
- else
110
- exception
111
- end
153
+ def causes_for(exception)
154
+ return enum_for(__method__, exception) unless block_given?
155
+
156
+ yield exception while exception = exception.cause
157
+ end
158
+
159
+ def wrapped_causes_for(exception, backtrace_cleaner)
160
+ causes_for(exception).map { |cause| self.class.new(backtrace_cleaner, cause) }
112
161
  end
113
162
 
114
163
  def clean_backtrace(*args)
@@ -9,10 +9,13 @@ module ActionDispatch
9
9
  end
10
10
 
11
11
  def call(env)
12
- state = @executor.run!
12
+ state = @executor.run!(reset: true)
13
13
  begin
14
14
  response = @app.call(env)
15
15
  returned = response << ::Rack::BodyProxy.new(response.pop) { state.complete! }
16
+ rescue => error
17
+ @executor.error_reporter.report(error, handled: false)
18
+ raise
16
19
  ensure
17
20
  state.complete! unless returned
18
21
  end
@@ -38,7 +38,7 @@ module ActionDispatch
38
38
  #
39
39
  # See docs on the FlashHash class for more details about the flash.
40
40
  class Flash
41
- KEY = "action_dispatch.request.flash_hash".freeze
41
+ KEY = "action_dispatch.request.flash_hash"
42
42
 
43
43
  module RequestMethods
44
44
  # Access the contents of the flash. Use <tt>flash["notice"]</tt> to
@@ -59,16 +59,14 @@ module ActionDispatch
59
59
  end
60
60
 
61
61
  def commit_flash # :nodoc:
62
- session = self.session || {}
63
- flash_hash = self.flash_hash
62
+ return unless session.enabled?
64
63
 
65
64
  if flash_hash && (flash_hash.present? || session.key?("flash"))
66
65
  session["flash"] = flash_hash.to_session_value
67
66
  self.flash = flash_hash.dup
68
67
  end
69
68
 
70
- if (!session.respond_to?(:loaded?) || session.loaded?) && # reset_session uses {}, which doesn't implement #loaded?
71
- session.key?("flash") && session["flash"].nil?
69
+ if session.loaded? && session.key?("flash") && session["flash"].nil?
72
70
  session.delete("flash")
73
71
  end
74
72
  end
@@ -79,7 +77,7 @@ module ActionDispatch
79
77
  end
80
78
  end
81
79
 
82
- class FlashNow #:nodoc:
80
+ class FlashNow # :nodoc:
83
81
  attr_accessor :flash
84
82
 
85
83
  def initialize(flash)
@@ -111,7 +109,7 @@ module ActionDispatch
111
109
  class FlashHash
112
110
  include Enumerable
113
111
 
114
- def self.from_session_value(value) #:nodoc:
112
+ def self.from_session_value(value) # :nodoc:
115
113
  case value
116
114
  when FlashHash # Rails 3.1, 3.2
117
115
  flashes = value.instance_variable_get(:@flashes)
@@ -132,13 +130,13 @@ module ActionDispatch
132
130
 
133
131
  # Builds a hash containing the flashes to keep for the next request.
134
132
  # If there are none to keep, returns +nil+.
135
- def to_session_value #:nodoc:
133
+ def to_session_value # :nodoc:
136
134
  flashes_to_keep = @flashes.except(*@discard)
137
135
  return nil if flashes_to_keep.empty?
138
136
  { "discard" => [], "flashes" => flashes_to_keep }
139
137
  end
140
138
 
141
- def initialize(flashes = {}, discard = []) #:nodoc:
139
+ def initialize(flashes = {}, discard = []) # :nodoc:
142
140
  @discard = Set.new(stringify_array(discard))
143
141
  @flashes = flashes.stringify_keys
144
142
  @now = nil
@@ -162,7 +160,7 @@ module ActionDispatch
162
160
  @flashes[k.to_s]
163
161
  end
164
162
 
165
- def update(h) #:nodoc:
163
+ def update(h) # :nodoc:
166
164
  @discard.subtract stringify_array(h.keys)
167
165
  @flashes.update h.stringify_keys
168
166
  self
@@ -202,7 +200,7 @@ module ActionDispatch
202
200
 
203
201
  alias :merge! :update
204
202
 
205
- def replace(h) #:nodoc:
203
+ def replace(h) # :nodoc:
206
204
  @discard.clear
207
205
  @flashes.replace h.stringify_keys
208
206
  self
@@ -253,7 +251,7 @@ module ActionDispatch
253
251
  # Mark for removal entries that were kept, and delete unkept ones.
254
252
  #
255
253
  # This method is called automatically by filters, so you generally don't need to care about it.
256
- def sweep #:nodoc:
254
+ def sweep # :nodoc:
257
255
  @discard.each { |k| @flashes.delete k }
258
256
  @discard.replace @flashes.keys
259
257
  end