actionpack 6.1.7.5 → 7.1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (160) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +355 -435
  3. data/MIT-LICENSE +2 -1
  4. data/README.rdoc +6 -7
  5. data/lib/abstract_controller/asset_paths.rb +1 -1
  6. data/lib/abstract_controller/base.rb +33 -37
  7. data/lib/abstract_controller/caching/fragments.rb +4 -2
  8. data/lib/abstract_controller/caching.rb +1 -1
  9. data/lib/abstract_controller/callbacks.rb +50 -11
  10. data/lib/abstract_controller/collector.rb +2 -2
  11. data/lib/abstract_controller/deprecator.rb +7 -0
  12. data/lib/abstract_controller/error.rb +1 -1
  13. data/lib/abstract_controller/helpers.rb +78 -30
  14. data/lib/abstract_controller/logger.rb +1 -1
  15. data/lib/abstract_controller/railties/routes_helpers.rb +3 -16
  16. data/lib/abstract_controller/rendering.rb +12 -14
  17. data/lib/abstract_controller/translation.rb +26 -7
  18. data/lib/abstract_controller/url_for.rb +6 -6
  19. data/lib/abstract_controller.rb +6 -0
  20. data/lib/action_controller/api.rb +12 -10
  21. data/lib/action_controller/base.rb +8 -21
  22. data/lib/action_controller/caching.rb +2 -0
  23. data/lib/action_controller/deprecator.rb +7 -0
  24. data/lib/action_controller/form_builder.rb +4 -2
  25. data/lib/action_controller/log_subscriber.rb +20 -7
  26. data/lib/action_controller/metal/basic_implicit_render.rb +3 -1
  27. data/lib/action_controller/metal/conditional_get.rb +137 -102
  28. data/lib/action_controller/metal/content_security_policy.rb +37 -3
  29. data/lib/action_controller/metal/cookies.rb +1 -1
  30. data/lib/action_controller/metal/data_streaming.rb +25 -31
  31. data/lib/action_controller/metal/default_headers.rb +2 -0
  32. data/lib/action_controller/metal/etag_with_flash.rb +3 -1
  33. data/lib/action_controller/metal/etag_with_template_digest.rb +2 -0
  34. data/lib/action_controller/metal/exceptions.rb +27 -30
  35. data/lib/action_controller/metal/flash.rb +6 -2
  36. data/lib/action_controller/metal/head.rb +9 -7
  37. data/lib/action_controller/metal/helpers.rb +5 -16
  38. data/lib/action_controller/metal/http_authentication.rb +78 -42
  39. data/lib/action_controller/metal/implicit_render.rb +5 -3
  40. data/lib/action_controller/metal/instrumentation.rb +62 -50
  41. data/lib/action_controller/metal/live.rb +67 -2
  42. data/lib/action_controller/metal/mime_responds.rb +5 -5
  43. data/lib/action_controller/metal/params_wrapper.rb +24 -13
  44. data/lib/action_controller/metal/permissions_policy.rb +20 -29
  45. data/lib/action_controller/metal/redirecting.rb +96 -23
  46. data/lib/action_controller/metal/renderers.rb +14 -15
  47. data/lib/action_controller/metal/rendering.rb +121 -16
  48. data/lib/action_controller/metal/request_forgery_protection.rb +208 -68
  49. data/lib/action_controller/metal/rescue.rb +7 -4
  50. data/lib/action_controller/metal/streaming.rb +74 -36
  51. data/lib/action_controller/metal/strong_parameters.rb +254 -151
  52. data/lib/action_controller/metal/testing.rb +9 -2
  53. data/lib/action_controller/metal/url_for.rb +10 -5
  54. data/lib/action_controller/metal.rb +89 -34
  55. data/lib/action_controller/railtie.rb +66 -9
  56. data/lib/action_controller/renderer.rb +99 -85
  57. data/lib/action_controller/test_case.rb +42 -11
  58. data/lib/action_controller.rb +10 -6
  59. data/lib/action_dispatch/constants.rb +32 -0
  60. data/lib/action_dispatch/deprecator.rb +7 -0
  61. data/lib/action_dispatch/http/cache.rb +21 -16
  62. data/lib/action_dispatch/http/content_security_policy.rb +122 -44
  63. data/lib/action_dispatch/http/filter_parameters.rb +14 -23
  64. data/lib/action_dispatch/http/headers.rb +3 -1
  65. data/lib/action_dispatch/http/mime_negotiation.rb +25 -15
  66. data/lib/action_dispatch/http/mime_type.rb +43 -22
  67. data/lib/action_dispatch/http/mime_types.rb +3 -1
  68. data/lib/action_dispatch/http/parameters.rb +6 -6
  69. data/lib/action_dispatch/http/permissions_policy.rb +57 -19
  70. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  71. data/lib/action_dispatch/http/request.rb +75 -51
  72. data/lib/action_dispatch/http/response.rb +81 -77
  73. data/lib/action_dispatch/http/upload.rb +15 -2
  74. data/lib/action_dispatch/http/url.rb +11 -19
  75. data/lib/action_dispatch/journey/formatter.rb +8 -2
  76. data/lib/action_dispatch/journey/gtg/builder.rb +11 -12
  77. data/lib/action_dispatch/journey/gtg/simulator.rb +10 -4
  78. data/lib/action_dispatch/journey/gtg/transition_table.rb +77 -21
  79. data/lib/action_dispatch/journey/nodes/node.rb +70 -5
  80. data/lib/action_dispatch/journey/path/pattern.rb +36 -27
  81. data/lib/action_dispatch/journey/route.rb +8 -14
  82. data/lib/action_dispatch/journey/router/utils.rb +2 -2
  83. data/lib/action_dispatch/journey/router.rb +10 -9
  84. data/lib/action_dispatch/journey/routes.rb +5 -5
  85. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  86. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  87. data/lib/action_dispatch/log_subscriber.rb +23 -0
  88. data/lib/action_dispatch/middleware/actionable_exceptions.rb +5 -7
  89. data/lib/action_dispatch/middleware/assume_ssl.rb +24 -0
  90. data/lib/action_dispatch/middleware/callbacks.rb +2 -0
  91. data/lib/action_dispatch/middleware/cookies.rb +97 -107
  92. data/lib/action_dispatch/middleware/debug_exceptions.rb +31 -28
  93. data/lib/action_dispatch/middleware/debug_locks.rb +7 -4
  94. data/lib/action_dispatch/middleware/debug_view.rb +7 -2
  95. data/lib/action_dispatch/middleware/exception_wrapper.rb +190 -27
  96. data/lib/action_dispatch/middleware/executor.rb +3 -0
  97. data/lib/action_dispatch/middleware/flash.rb +24 -18
  98. data/lib/action_dispatch/middleware/host_authorization.rb +19 -20
  99. data/lib/action_dispatch/middleware/public_exceptions.rb +5 -3
  100. data/lib/action_dispatch/middleware/reloader.rb +7 -5
  101. data/lib/action_dispatch/middleware/remote_ip.rb +32 -19
  102. data/lib/action_dispatch/middleware/request_id.rb +5 -3
  103. data/lib/action_dispatch/middleware/server_timing.rb +76 -0
  104. data/lib/action_dispatch/middleware/session/abstract_store.rb +6 -1
  105. data/lib/action_dispatch/middleware/session/cache_store.rb +2 -0
  106. data/lib/action_dispatch/middleware/session/cookie_store.rb +19 -13
  107. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +3 -1
  108. data/lib/action_dispatch/middleware/show_exceptions.rb +30 -25
  109. data/lib/action_dispatch/middleware/ssl.rb +18 -6
  110. data/lib/action_dispatch/middleware/stack.rb +34 -11
  111. data/lib/action_dispatch/middleware/static.rb +16 -16
  112. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
  113. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +5 -5
  114. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +4 -11
  115. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
  116. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +2 -2
  117. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +10 -5
  118. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +7 -3
  119. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +9 -9
  120. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
  121. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +3 -3
  122. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +45 -18
  123. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -15
  124. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +4 -4
  125. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +6 -6
  126. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +7 -7
  127. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
  128. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  129. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +3 -0
  130. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +64 -55
  131. data/lib/action_dispatch/railtie.rb +20 -4
  132. data/lib/action_dispatch/request/session.rb +59 -19
  133. data/lib/action_dispatch/request/utils.rb +8 -3
  134. data/lib/action_dispatch/routing/inspector.rb +55 -7
  135. data/lib/action_dispatch/routing/mapper.rb +117 -107
  136. data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -0
  137. data/lib/action_dispatch/routing/redirection.rb +20 -8
  138. data/lib/action_dispatch/routing/route_set.rb +67 -27
  139. data/lib/action_dispatch/routing/routes_proxy.rb +11 -16
  140. data/lib/action_dispatch/routing/url_for.rb +29 -26
  141. data/lib/action_dispatch/routing.rb +12 -13
  142. data/lib/action_dispatch/system_test_case.rb +8 -8
  143. data/lib/action_dispatch/system_testing/browser.rb +20 -29
  144. data/lib/action_dispatch/system_testing/driver.rb +34 -18
  145. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +35 -20
  146. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +0 -8
  147. data/lib/action_dispatch/testing/assertion_response.rb +1 -1
  148. data/lib/action_dispatch/testing/assertions/response.rb +14 -7
  149. data/lib/action_dispatch/testing/assertions/routing.rb +70 -30
  150. data/lib/action_dispatch/testing/assertions.rb +3 -4
  151. data/lib/action_dispatch/testing/integration.rb +33 -25
  152. data/lib/action_dispatch/testing/request_encoder.rb +4 -1
  153. data/lib/action_dispatch/testing/test_process.rb +5 -30
  154. data/lib/action_dispatch/testing/test_request.rb +1 -1
  155. data/lib/action_dispatch/testing/test_response.rb +34 -2
  156. data/lib/action_dispatch.rb +38 -4
  157. data/lib/action_pack/gem_version.rb +4 -4
  158. data/lib/action_pack/version.rb +1 -1
  159. data/lib/action_pack.rb +1 -1
  160. metadata +67 -30
@@ -1,12 +1,13 @@
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
5
 
7
6
  require "action_view"
8
7
 
9
8
  module ActionDispatch
9
+ # = Action Dispatch \DebugExceptions
10
+ #
10
11
  # This middleware is responsible for logging exceptions and
11
12
  # showing a debugging page in case the request is local.
12
13
  class DebugExceptions
@@ -25,26 +26,26 @@ module ActionDispatch
25
26
  end
26
27
 
27
28
  def call(env)
28
- request = ActionDispatch::Request.new env
29
29
  _, headers, body = response = @app.call(env)
30
30
 
31
- if headers["X-Cascade"] == "pass"
31
+ if headers[Constants::X_CASCADE] == "pass"
32
32
  body.close if body.respond_to?(:close)
33
33
  raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
34
34
  end
35
35
 
36
36
  response
37
37
  rescue Exception => exception
38
- invoke_interceptors(request, exception)
39
- raise exception unless request.show_exceptions?
40
- 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)
41
45
  end
42
46
 
43
47
  private
44
- def invoke_interceptors(request, exception)
45
- backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
46
- wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
47
-
48
+ def invoke_interceptors(request, exception, wrapper)
48
49
  @interceptors.each do |interceptor|
49
50
  interceptor.call(request, exception)
50
51
  rescue Exception
@@ -52,9 +53,7 @@ module ActionDispatch
52
53
  end
53
54
  end
54
55
 
55
- def render_exception(request, exception)
56
- backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
57
- wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
56
+ def render_exception(request, exception, wrapper)
58
57
  log_error(request, wrapper)
59
58
 
60
59
  if request.get_header("action_dispatch.show_detailed_exceptions")
@@ -95,7 +94,7 @@ module ActionDispatch
95
94
  wrapper.status_code,
96
95
  Rack::Utils::HTTP_STATUS_CODES[500]
97
96
  ),
98
- exception: wrapper.exception.inspect,
97
+ exception: wrapper.exception_inspect,
99
98
  traces: wrapper.traces
100
99
  }
101
100
 
@@ -116,48 +115,48 @@ module ActionDispatch
116
115
  DebugView.new(
117
116
  request: request,
118
117
  exception_wrapper: wrapper,
118
+ # Everything should use the wrapper, but we need to pass
119
+ # `exception` for legacy code.
119
120
  exception: wrapper.exception,
120
121
  traces: wrapper.traces,
121
122
  show_source_idx: wrapper.source_to_show_id,
122
123
  trace_to_show: wrapper.trace_to_show,
123
- routes_inspector: routes_inspector(wrapper.exception),
124
+ routes_inspector: routes_inspector(wrapper),
124
125
  source_extracts: wrapper.source_extracts,
125
- line_number: wrapper.line_number,
126
- file: wrapper.file
127
126
  )
128
127
  end
129
128
 
130
129
  def render(status, body, format)
131
- [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]]
132
131
  end
133
132
 
134
133
  def log_error(request, wrapper)
135
134
  logger = logger(request)
136
135
 
137
136
  return unless logger
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, array)
153
- lines = Array(array)
154
-
151
+ def log_array(logger, lines, request)
155
152
  return if lines.empty?
156
153
 
154
+ level = request.get_header("action_dispatch.debug_exception_log_level")
155
+
157
156
  if logger.formatter && logger.formatter.respond_to?(:tags_text)
158
- logger.fatal lines.join("\n#{logger.formatter.tags_text}")
157
+ logger.add(level, lines.join("\n#{logger.formatter.tags_text}"))
159
158
  else
160
- logger.fatal lines.join("\n")
159
+ logger.add(level, lines.join("\n"))
161
160
  end
162
161
  end
163
162
 
@@ -170,7 +169,7 @@ module ActionDispatch
170
169
  end
171
170
 
172
171
  def routes_inspector(exception)
173
- 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?)
174
173
  ActionDispatch::Routing::RoutesInspector.new(@routes_app.routes.routes)
175
174
  end
176
175
  end
@@ -178,5 +177,9 @@ module ActionDispatch
178
177
  def api_request?(content_type)
179
178
  @response_format == :api && !content_type.html?
180
179
  end
180
+
181
+ def log_rescued_responses?(request)
182
+ request.get_header("action_dispatch.log_rescued_responses")
183
+ end
181
184
  end
182
185
  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
@@ -9,9 +11,9 @@ module ActionDispatch
9
11
  # config.middleware.insert_before Rack::Sendfile, ActionDispatch::DebugLocks
10
12
  #
11
13
  # 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.
14
+ # the route <tt>/rails/locks</tt> will show a summary of all threads currently
15
+ # known to the interlock, which lock level they are holding or awaiting, and
16
+ # their current backtrace.
15
17
  #
16
18
  # Generally a deadlock will be caused by the interlock conflicting with some
17
19
  # other external lock or blocking I/O call. These cannot be automatically
@@ -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
62
+
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
51
98
 
52
- expand_backtrace if exception.is_a?(SyntaxError) || exception.cause.is_a?(SyntaxError)
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
58
106
  else
59
- exception
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
114
+ else
115
+ []
60
116
  end
61
117
  end
62
118
 
@@ -118,17 +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
+
200
+ def rescue_response?
201
+ @@rescue_responses.key?(exception.class.name)
202
+ end
203
+
121
204
  def source_extracts
122
205
  backtrace.map do |trace|
123
- file, line_number = extract_file_and_line_number(trace)
124
-
125
- {
126
- code: source_fragment(file, line_number),
127
- line_number: line_number
128
- }
206
+ extract_source(trace)
129
207
  end
130
208
  end
131
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
+
132
215
  def trace_to_show
133
216
  if traces["Application Trace"].empty? && rescue_template != "routing_error"
134
217
  "Full Trace"
@@ -141,9 +224,65 @@ module ActionDispatch
141
224
  (traces[trace_to_show].first || {})[:id]
142
225
  end
143
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
+
144
243
  private
145
- def backtrace
146
- 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
147
286
  end
148
287
 
149
288
  def causes_for(exception)
@@ -164,29 +303,53 @@ module ActionDispatch
164
303
  end
165
304
  end
166
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
+
167
341
  def source_fragment(path, line)
168
342
  return unless Rails.respond_to?(:root) && Rails.root
169
343
  full_path = Rails.root.join(path)
170
344
  if File.exist?(full_path)
171
345
  File.open(full_path, "r") do |file|
172
- start = [line - 3, 0].max
173
- lines = file.each_line.drop(start).take(6)
174
- Hash[*(start + 1..(lines.count + start)).zip(lines).flatten]
346
+ extract_source_fragment_lines(file.each_line, line)
175
347
  end
176
348
  end
177
349
  end
178
350
 
179
351
  def extract_file_and_line_number(trace)
180
- # Split by the first colon followed by some digits, which works for both
181
- # Windows and Unix path styles.
182
- file, line = trace.match(/^(.+?):(\d+).*$/, &:captures) || trace
183
- [file, line.to_i]
184
- end
185
-
186
- def expand_backtrace
187
- @exception.backtrace.unshift(
188
- @exception.to_s.split("\n")
189
- ).flatten!
352
+ [trace.path, trace.lineno]
190
353
  end
191
354
  end
192
355
  end
@@ -13,6 +13,9 @@ module ActionDispatch
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, source: "application.action_dispatch")
18
+ raise
16
19
  ensure
17
20
  state.complete! unless returned
18
21
  end
@@ -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
@@ -20,10 +22,11 @@ module ActionDispatch
20
22
  # end
21
23
  # end
22
24
  #
23
- # show.html.erb
24
- # <% if flash[:notice] %>
25
- # <div class="notice"><%= flash[:notice] %></div>
26
- # <% end %>
25
+ # Then in +show.html.erb+:
26
+ #
27
+ # <% if flash[:notice] %>
28
+ # <div class="notice"><%= flash[:notice] %></div>
29
+ # <% end %>
27
30
  #
28
31
  # Since the +notice+ and +alert+ keys are a common idiom, convenience accessors are available:
29
32
  #
@@ -41,9 +44,9 @@ module ActionDispatch
41
44
  KEY = "action_dispatch.request.flash_hash"
42
45
 
43
46
  module RequestMethods
44
- # Access the contents of the flash. Use <tt>flash["notice"]</tt> to
45
- # read a notice you put there or <tt>flash["notice"] = "hello"</tt>
46
- # to put a new one.
47
+ # Access the contents of the flash. Returns a ActionDispatch::Flash::FlashHash.
48
+ #
49
+ # See ActionDispatch::Flash for example usage.
47
50
  def flash
48
51
  flash = flash_hash
49
52
  return flash if flash
@@ -59,16 +62,14 @@ module ActionDispatch
59
62
  end
60
63
 
61
64
  def commit_flash # :nodoc:
62
- session = self.session || {}
63
- flash_hash = self.flash_hash
65
+ return unless session.enabled?
64
66
 
65
67
  if flash_hash && (flash_hash.present? || session.key?("flash"))
66
68
  session["flash"] = flash_hash.to_session_value
67
69
  self.flash = flash_hash.dup
68
70
  end
69
71
 
70
- if (!session.respond_to?(:loaded?) || session.loaded?) && # reset_session uses {}, which doesn't implement #loaded?
71
- session.key?("flash") && session["flash"].nil?
72
+ if session.loaded? && session.key?("flash") && session["flash"].nil?
72
73
  session.delete("flash")
73
74
  end
74
75
  end
@@ -79,7 +80,7 @@ module ActionDispatch
79
80
  end
80
81
  end
81
82
 
82
- class FlashNow #:nodoc:
83
+ class FlashNow # :nodoc:
83
84
  attr_accessor :flash
84
85
 
85
86
  def initialize(flash)
@@ -111,7 +112,7 @@ module ActionDispatch
111
112
  class FlashHash
112
113
  include Enumerable
113
114
 
114
- def self.from_session_value(value) #:nodoc:
115
+ def self.from_session_value(value) # :nodoc:
115
116
  case value
116
117
  when FlashHash # Rails 3.1, 3.2
117
118
  flashes = value.instance_variable_get(:@flashes)
@@ -132,13 +133,13 @@ module ActionDispatch
132
133
 
133
134
  # Builds a hash containing the flashes to keep for the next request.
134
135
  # If there are none to keep, returns +nil+.
135
- def to_session_value #:nodoc:
136
+ def to_session_value # :nodoc:
136
137
  flashes_to_keep = @flashes.except(*@discard)
137
138
  return nil if flashes_to_keep.empty?
138
139
  { "discard" => [], "flashes" => flashes_to_keep }
139
140
  end
140
141
 
141
- def initialize(flashes = {}, discard = []) #:nodoc:
142
+ def initialize(flashes = {}, discard = []) # :nodoc:
142
143
  @discard = Set.new(stringify_array(discard))
143
144
  @flashes = flashes.stringify_keys
144
145
  @now = nil
@@ -162,7 +163,7 @@ module ActionDispatch
162
163
  @flashes[k.to_s]
163
164
  end
164
165
 
165
- def update(h) #:nodoc:
166
+ def update(h) # :nodoc:
166
167
  @discard.subtract stringify_array(h.keys)
167
168
  @flashes.update h.stringify_keys
168
169
  self
@@ -176,6 +177,8 @@ module ActionDispatch
176
177
  @flashes.key? name.to_s
177
178
  end
178
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.
179
182
  def delete(key)
180
183
  key = key.to_s
181
184
  @discard.delete key
@@ -202,7 +205,7 @@ module ActionDispatch
202
205
 
203
206
  alias :merge! :update
204
207
 
205
- def replace(h) #:nodoc:
208
+ def replace(h) # :nodoc:
206
209
  @discard.clear
207
210
  @flashes.replace h.stringify_keys
208
211
  self
@@ -244,6 +247,9 @@ module ActionDispatch
244
247
  #
245
248
  # flash.discard # discard the entire flash at the end of the current action
246
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.
247
253
  def discard(k = nil)
248
254
  k = k.to_s if k
249
255
  @discard.merge Array(k || keys)
@@ -253,7 +259,7 @@ module ActionDispatch
253
259
  # Mark for removal entries that were kept, and delete unkept ones.
254
260
  #
255
261
  # This method is called automatically by filters, so you generally don't need to care about it.
256
- def sweep #:nodoc:
262
+ def sweep # :nodoc:
257
263
  @discard.each { |k| @flashes.delete k }
258
264
  @discard.replace @flashes.keys
259
265
  end