actionpack 5.2.4.4 → 6.1.1

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 (155) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +264 -322
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -3
  5. data/lib/abstract_controller.rb +1 -0
  6. data/lib/abstract_controller/base.rb +38 -4
  7. data/lib/abstract_controller/caching.rb +1 -1
  8. data/lib/abstract_controller/caching/fragments.rb +6 -22
  9. data/lib/abstract_controller/callbacks.rb +14 -2
  10. data/lib/abstract_controller/collector.rb +1 -2
  11. data/lib/abstract_controller/helpers.rb +106 -90
  12. data/lib/abstract_controller/railties/routes_helpers.rb +1 -1
  13. data/lib/abstract_controller/rendering.rb +9 -9
  14. data/lib/abstract_controller/translation.rb +11 -5
  15. data/lib/action_controller.rb +7 -4
  16. data/lib/action_controller/api.rb +4 -3
  17. data/lib/action_controller/base.rb +6 -9
  18. data/lib/action_controller/caching.rb +1 -3
  19. data/lib/action_controller/log_subscriber.rb +10 -7
  20. data/lib/action_controller/metal.rb +10 -8
  21. data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
  22. data/lib/action_controller/metal/conditional_get.rb +19 -5
  23. data/lib/action_controller/metal/content_security_policy.rb +1 -2
  24. data/lib/action_controller/metal/cookies.rb +3 -1
  25. data/lib/action_controller/metal/data_streaming.rb +6 -7
  26. data/lib/action_controller/metal/default_headers.rb +17 -0
  27. data/lib/action_controller/metal/etag_with_template_digest.rb +3 -5
  28. data/lib/action_controller/metal/exceptions.rb +56 -2
  29. data/lib/action_controller/metal/flash.rb +5 -5
  30. data/lib/action_controller/metal/head.rb +7 -4
  31. data/lib/action_controller/metal/helpers.rb +14 -5
  32. data/lib/action_controller/metal/http_authentication.rb +24 -23
  33. data/lib/action_controller/metal/implicit_render.rb +5 -15
  34. data/lib/action_controller/metal/instrumentation.rb +13 -14
  35. data/lib/action_controller/metal/live.rb +30 -32
  36. data/lib/action_controller/metal/logging.rb +20 -0
  37. data/lib/action_controller/metal/mime_responds.rb +19 -4
  38. data/lib/action_controller/metal/parameter_encoding.rb +35 -4
  39. data/lib/action_controller/metal/params_wrapper.rb +31 -22
  40. data/lib/action_controller/metal/permissions_policy.rb +46 -0
  41. data/lib/action_controller/metal/redirecting.rb +6 -6
  42. data/lib/action_controller/metal/renderers.rb +4 -4
  43. data/lib/action_controller/metal/rendering.rb +8 -3
  44. data/lib/action_controller/metal/request_forgery_protection.rb +62 -34
  45. data/lib/action_controller/metal/rescue.rb +1 -1
  46. data/lib/action_controller/metal/streaming.rb +0 -1
  47. data/lib/action_controller/metal/strong_parameters.rb +167 -58
  48. data/lib/action_controller/metal/url_for.rb +1 -1
  49. data/lib/action_controller/railties/helpers.rb +1 -1
  50. data/lib/action_controller/renderer.rb +37 -13
  51. data/lib/action_controller/template_assertions.rb +1 -1
  52. data/lib/action_controller/test_case.rb +70 -65
  53. data/lib/action_dispatch.rb +9 -3
  54. data/lib/action_dispatch/http/cache.rb +26 -21
  55. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  56. data/lib/action_dispatch/http/content_security_policy.rb +33 -19
  57. data/lib/action_dispatch/http/filter_parameters.rb +9 -8
  58. data/lib/action_dispatch/http/filter_redirect.rb +2 -3
  59. data/lib/action_dispatch/http/headers.rb +4 -4
  60. data/lib/action_dispatch/http/mime_negotiation.rb +26 -13
  61. data/lib/action_dispatch/http/mime_type.rb +42 -23
  62. data/lib/action_dispatch/http/parameters.rb +14 -23
  63. data/lib/action_dispatch/http/permissions_policy.rb +173 -0
  64. data/lib/action_dispatch/http/request.rb +45 -22
  65. data/lib/action_dispatch/http/response.rb +45 -25
  66. data/lib/action_dispatch/http/upload.rb +9 -1
  67. data/lib/action_dispatch/http/url.rb +82 -82
  68. data/lib/action_dispatch/journey.rb +0 -2
  69. data/lib/action_dispatch/journey/formatter.rb +54 -30
  70. data/lib/action_dispatch/journey/gtg/builder.rb +22 -37
  71. data/lib/action_dispatch/journey/gtg/simulator.rb +8 -7
  72. data/lib/action_dispatch/journey/gtg/transition_table.rb +6 -5
  73. data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
  74. data/lib/action_dispatch/journey/nodes/node.rb +13 -11
  75. data/lib/action_dispatch/journey/parser.rb +13 -13
  76. data/lib/action_dispatch/journey/parser.y +1 -1
  77. data/lib/action_dispatch/journey/path/pattern.rb +19 -21
  78. data/lib/action_dispatch/journey/route.rb +10 -20
  79. data/lib/action_dispatch/journey/router.rb +26 -34
  80. data/lib/action_dispatch/journey/router/utils.rb +14 -12
  81. data/lib/action_dispatch/journey/routes.rb +0 -2
  82. data/lib/action_dispatch/journey/scanner.rb +10 -4
  83. data/lib/action_dispatch/journey/visitors.rb +1 -4
  84. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  85. data/lib/action_dispatch/middleware/callbacks.rb +2 -4
  86. data/lib/action_dispatch/middleware/cookies.rb +128 -109
  87. data/lib/action_dispatch/middleware/debug_exceptions.rb +43 -66
  88. data/lib/action_dispatch/middleware/debug_locks.rb +5 -5
  89. data/lib/action_dispatch/middleware/debug_view.rb +66 -0
  90. data/lib/action_dispatch/middleware/exception_wrapper.rb +75 -30
  91. data/lib/action_dispatch/middleware/flash.rb +1 -1
  92. data/lib/action_dispatch/middleware/host_authorization.rb +121 -0
  93. data/lib/action_dispatch/middleware/public_exceptions.rb +6 -3
  94. data/lib/action_dispatch/middleware/remote_ip.rb +14 -16
  95. data/lib/action_dispatch/middleware/request_id.rb +5 -6
  96. data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -3
  97. data/lib/action_dispatch/middleware/session/cookie_store.rb +3 -9
  98. data/lib/action_dispatch/middleware/show_exceptions.rb +3 -2
  99. data/lib/action_dispatch/middleware/ssl.rb +20 -15
  100. data/lib/action_dispatch/middleware/stack.rb +56 -2
  101. data/lib/action_dispatch/middleware/static.rb +153 -93
  102. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  103. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  104. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  105. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +3 -1
  106. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  107. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +4 -2
  108. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +45 -35
  109. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -0
  110. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -0
  111. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +23 -4
  112. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
  113. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +6 -3
  114. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +3 -1
  115. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +104 -8
  116. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  117. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +2 -2
  119. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -1
  120. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +2 -2
  121. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  122. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +24 -1
  123. data/lib/action_dispatch/railtie.rb +8 -2
  124. data/lib/action_dispatch/request/session.rb +10 -9
  125. data/lib/action_dispatch/request/utils.rb +26 -2
  126. data/lib/action_dispatch/routing.rb +21 -20
  127. data/lib/action_dispatch/routing/inspector.rb +100 -52
  128. data/lib/action_dispatch/routing/mapper.rb +155 -103
  129. data/lib/action_dispatch/routing/polymorphic_routes.rb +13 -15
  130. data/lib/action_dispatch/routing/redirection.rb +3 -3
  131. data/lib/action_dispatch/routing/route_set.rb +71 -69
  132. data/lib/action_dispatch/routing/url_for.rb +2 -2
  133. data/lib/action_dispatch/system_test_case.rb +54 -11
  134. data/lib/action_dispatch/system_testing/browser.rb +53 -16
  135. data/lib/action_dispatch/system_testing/driver.rb +11 -3
  136. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +49 -7
  137. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +8 -10
  138. data/lib/action_dispatch/testing/assertion_response.rb +0 -1
  139. data/lib/action_dispatch/testing/assertions.rb +1 -1
  140. data/lib/action_dispatch/testing/assertions/response.rb +4 -7
  141. data/lib/action_dispatch/testing/assertions/routing.rb +20 -8
  142. data/lib/action_dispatch/testing/integration.rb +61 -28
  143. data/lib/action_dispatch/testing/request_encoder.rb +2 -2
  144. data/lib/action_dispatch/testing/test_process.rb +29 -4
  145. data/lib/action_dispatch/testing/test_request.rb +3 -3
  146. data/lib/action_dispatch/testing/test_response.rb +4 -32
  147. data/lib/action_pack.rb +1 -1
  148. data/lib/action_pack/gem_version.rb +4 -4
  149. metadata +38 -26
  150. data/lib/action_controller/metal/force_ssl.rb +0 -99
  151. data/lib/action_dispatch/http/parameter_filter.rb +0 -86
  152. data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
  153. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -49
  154. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -120
  155. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +0 -26
@@ -3,57 +3,25 @@
3
3
  require "action_dispatch/http/request"
4
4
  require "action_dispatch/middleware/exception_wrapper"
5
5
  require "action_dispatch/routing/inspector"
6
- require "action_view"
7
- require "action_view/base"
8
6
 
9
- require "pp"
7
+ require "action_view"
10
8
 
11
9
  module ActionDispatch
12
10
  # This middleware is responsible for logging exceptions and
13
11
  # showing a debugging page in case the request is local.
14
12
  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")
22
-
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
13
+ cattr_reader :interceptors, instance_accessor: false, default: []
44
14
 
45
- if logger && logger.respond_to?(:silence)
46
- logger.silence { super }
47
- else
48
- super
49
- end
50
- end
15
+ def self.register_interceptor(object = nil, &block)
16
+ interceptor = object || block
17
+ interceptors << interceptor
51
18
  end
52
19
 
53
- def initialize(app, routes_app = nil, response_format = :default)
20
+ def initialize(app, routes_app = nil, response_format = :default, interceptors = self.class.interceptors)
54
21
  @app = app
55
22
  @routes_app = routes_app
56
23
  @response_format = response_format
24
+ @interceptors = interceptors
57
25
  end
58
26
 
59
27
  def call(env)
@@ -67,11 +35,22 @@ module ActionDispatch
67
35
 
68
36
  response
69
37
  rescue Exception => exception
38
+ invoke_interceptors(request, exception)
70
39
  raise exception unless request.show_exceptions?
71
40
  render_exception(request, exception)
72
41
  end
73
42
 
74
43
  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
+ @interceptors.each do |interceptor|
49
+ interceptor.call(request, exception)
50
+ rescue Exception
51
+ log_error(request, wrapper)
52
+ end
53
+ end
75
54
 
76
55
  def render_exception(request, exception)
77
56
  backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
@@ -79,7 +58,11 @@ module ActionDispatch
79
58
  log_error(request, wrapper)
80
59
 
81
60
  if request.get_header("action_dispatch.show_detailed_exceptions")
82
- content_type = request.formats.first
61
+ begin
62
+ content_type = request.formats.first
63
+ rescue ActionDispatch::Http::MimeNegotiation::InvalidType
64
+ content_type = Mime[:text]
65
+ end
83
66
 
84
67
  if api_request?(content_type)
85
68
  render_for_api_request(content_type, wrapper)
@@ -130,23 +113,13 @@ module ActionDispatch
130
113
  end
131
114
 
132
115
  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],
116
+ DebugView.new(
145
117
  request: request,
118
+ exception_wrapper: wrapper,
146
119
  exception: wrapper.exception,
147
- traces: traces,
148
- show_source_idx: source_to_show_id,
149
- trace_to_show: trace_to_show,
120
+ traces: wrapper.traces,
121
+ show_source_idx: wrapper.source_to_show_id,
122
+ trace_to_show: wrapper.trace_to_show,
150
123
  routes_inspector: routes_inspector(wrapper.exception),
151
124
  source_extracts: wrapper.source_extracts,
152
125
  line_number: wrapper.line_number,
@@ -160,27 +133,31 @@ module ActionDispatch
160
133
 
161
134
  def log_error(request, wrapper)
162
135
  logger = logger(request)
136
+
163
137
  return unless logger
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
152
  def log_array(logger, array)
153
+ lines = Array(array)
154
+
155
+ return if lines.empty?
156
+
180
157
  if logger.formatter && logger.formatter.respond_to?(:tags_text)
181
- logger.fatal array.join("\n#{logger.formatter.tags_text}")
158
+ logger.fatal lines.join("\n#{logger.formatter.tags_text}")
182
159
  else
183
- logger.fatal array.join("\n")
160
+ logger.fatal lines.join("\n")
184
161
  end
185
162
  end
186
163
 
@@ -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
@@ -97,18 +129,31 @@ module ActionDispatch
97
129
  end
98
130
  end
99
131
 
100
- private
132
+ def trace_to_show
133
+ if traces["Application Trace"].empty? && rescue_template != "routing_error"
134
+ "Full Trace"
135
+ else
136
+ "Application Trace"
137
+ end
138
+ end
139
+
140
+ def source_to_show_id
141
+ (traces[trace_to_show].first || {})[:id]
142
+ end
101
143
 
144
+ private
102
145
  def backtrace
103
146
  Array(@exception.backtrace)
104
147
  end
105
148
 
106
- def original_exception(exception)
107
- if @@rescue_responses.has_key?(exception.cause.class.name)
108
- exception.cause
109
- else
110
- exception
111
- end
149
+ def causes_for(exception)
150
+ return enum_for(__method__, exception) unless block_given?
151
+
152
+ yield exception while exception = exception.cause
153
+ end
154
+
155
+ def wrapped_causes_for(exception, backtrace_cleaner)
156
+ causes_for(exception).map { |cause| self.class.new(backtrace_cleaner, cause) }
112
157
  end
113
158
 
114
159
  def clean_backtrace(*args)
@@ -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
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_dispatch/http/request"
4
+
5
+ module ActionDispatch
6
+ # This middleware guards from DNS rebinding attacks by explicitly permitting
7
+ # the hosts a request can be sent to, and is passed the options set in
8
+ # +config.host_authorization+.
9
+ #
10
+ # Requests can opt-out of Host Authorization with +exclude+:
11
+ #
12
+ # config.host_authorization = { exclude: ->(request) { request.path =~ /healthcheck/ } }
13
+ #
14
+ # When a request comes to an unauthorized host, the +response_app+
15
+ # application will be executed and rendered. If no +response_app+ is given, a
16
+ # default one will run, which responds with +403 Forbidden+.
17
+ class HostAuthorization
18
+ class Permissions # :nodoc:
19
+ def initialize(hosts)
20
+ @hosts = sanitize_hosts(hosts)
21
+ end
22
+
23
+ def empty?
24
+ @hosts.empty?
25
+ end
26
+
27
+ def allows?(host)
28
+ @hosts.any? do |allowed|
29
+ allowed === host
30
+ rescue
31
+ # IPAddr#=== raises an error if you give it a hostname instead of
32
+ # IP. Treat similar errors as blocked access.
33
+ false
34
+ end
35
+ end
36
+
37
+ private
38
+ def sanitize_hosts(hosts)
39
+ Array(hosts).map do |host|
40
+ case host
41
+ when Regexp then sanitize_regexp(host)
42
+ when String then sanitize_string(host)
43
+ else host
44
+ end
45
+ end
46
+ end
47
+
48
+ def sanitize_regexp(host)
49
+ /\A#{host}\z/
50
+ end
51
+
52
+ def sanitize_string(host)
53
+ if host.start_with?(".")
54
+ /\A(.+\.)?#{Regexp.escape(host[1..-1])}\z/i
55
+ else
56
+ /\A#{host}\z/i
57
+ end
58
+ end
59
+ end
60
+
61
+ DEFAULT_RESPONSE_APP = -> env do
62
+ request = Request.new(env)
63
+
64
+ format = request.xhr? ? "text/plain" : "text/html"
65
+ template = DebugView.new(host: request.host)
66
+ body = template.render(template: "rescues/blocked_host", layout: "rescues/layout")
67
+
68
+ [403, {
69
+ "Content-Type" => "#{format}; charset=#{Response.default_charset}",
70
+ "Content-Length" => body.bytesize.to_s,
71
+ }, [body]]
72
+ end
73
+
74
+ def initialize(app, hosts, deprecated_response_app = nil, exclude: nil, response_app: nil)
75
+ @app = app
76
+ @permissions = Permissions.new(hosts)
77
+ @exclude = exclude
78
+
79
+ unless deprecated_response_app.nil?
80
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
81
+ `action_dispatch.hosts_response_app` is deprecated and will be ignored in Rails 6.2.
82
+ Use the Host Authorization `response_app` setting instead.
83
+ MSG
84
+
85
+ response_app ||= deprecated_response_app
86
+ end
87
+
88
+ @response_app = response_app || DEFAULT_RESPONSE_APP
89
+ end
90
+
91
+ def call(env)
92
+ return @app.call(env) if @permissions.empty?
93
+
94
+ request = Request.new(env)
95
+
96
+ if authorized?(request) || excluded?(request)
97
+ mark_as_authorized(request)
98
+ @app.call(env)
99
+ else
100
+ @response_app.call(env)
101
+ end
102
+ end
103
+
104
+ private
105
+ def authorized?(request)
106
+ origin_host = request.get_header("HTTP_HOST").to_s.sub(/:\d+\z/, "")
107
+ forwarded_host = request.x_forwarded_host.to_s.split(/,\s?/).last.to_s.sub(/:\d+\z/, "")
108
+
109
+ @permissions.allows?(origin_host) &&
110
+ (forwarded_host.blank? || @permissions.allows?(forwarded_host))
111
+ end
112
+
113
+ def excluded?(request)
114
+ @exclude && @exclude.call(request)
115
+ end
116
+
117
+ def mark_as_authorized(request)
118
+ request.set_header("action_dispatch.authorized_host", request.host)
119
+ end
120
+ end
121
+ end