actionpack 4.2.11.1 → 6.1.3.2

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 (187) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +291 -489
  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 +81 -51
  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 +61 -33
  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 +115 -99
  13. data/lib/abstract_controller/logger.rb +2 -0
  14. data/lib/abstract_controller/railties/routes_helpers.rb +21 -3
  15. data/lib/abstract_controller/rendering.rb +48 -47
  16. data/lib/abstract_controller/translation.rb +17 -8
  17. data/lib/abstract_controller/url_for.rb +2 -0
  18. data/lib/abstract_controller.rb +13 -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 +29 -24
  22. data/lib/action_controller/caching.rb +12 -57
  23. data/lib/action_controller/form_builder.rb +50 -0
  24. data/lib/action_controller/log_subscriber.rb +17 -19
  25. data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
  26. data/lib/action_controller/metal/conditional_get.rb +134 -46
  27. data/lib/action_controller/metal/content_security_policy.rb +51 -0
  28. data/lib/action_controller/metal/cookies.rb +6 -4
  29. data/lib/action_controller/metal/data_streaming.rb +30 -50
  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 +21 -16
  33. data/lib/action_controller/metal/exceptions.rb +63 -15
  34. data/lib/action_controller/metal/flash.rb +9 -8
  35. data/lib/action_controller/metal/head.rb +26 -21
  36. data/lib/action_controller/metal/helpers.rb +37 -18
  37. data/lib/action_controller/metal/http_authentication.rb +81 -73
  38. data/lib/action_controller/metal/implicit_render.rb +53 -9
  39. data/lib/action_controller/metal/instrumentation.rb +32 -35
  40. data/lib/action_controller/metal/live.rb +102 -120
  41. data/lib/action_controller/metal/logging.rb +20 -0
  42. data/lib/action_controller/metal/mime_responds.rb +49 -47
  43. data/lib/action_controller/metal/parameter_encoding.rb +82 -0
  44. data/lib/action_controller/metal/params_wrapper.rb +83 -66
  45. data/lib/action_controller/metal/permissions_policy.rb +46 -0
  46. data/lib/action_controller/metal/redirecting.rb +53 -32
  47. data/lib/action_controller/metal/renderers.rb +87 -44
  48. data/lib/action_controller/metal/rendering.rb +77 -50
  49. data/lib/action_controller/metal/request_forgery_protection.rb +267 -103
  50. data/lib/action_controller/metal/rescue.rb +10 -17
  51. data/lib/action_controller/metal/streaming.rb +12 -11
  52. data/lib/action_controller/metal/strong_parameters.rb +714 -186
  53. data/lib/action_controller/metal/testing.rb +2 -17
  54. data/lib/action_controller/metal/url_for.rb +19 -10
  55. data/lib/action_controller/metal.rb +104 -87
  56. data/lib/action_controller/railtie.rb +28 -10
  57. data/lib/action_controller/railties/helpers.rb +3 -1
  58. data/lib/action_controller/renderer.rb +141 -0
  59. data/lib/action_controller/template_assertions.rb +11 -0
  60. data/lib/action_controller/test_case.rb +296 -422
  61. data/lib/action_controller.rb +34 -23
  62. data/lib/action_dispatch/http/cache.rb +107 -56
  63. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  64. data/lib/action_dispatch/http/content_security_policy.rb +286 -0
  65. data/lib/action_dispatch/http/filter_parameters.rb +32 -25
  66. data/lib/action_dispatch/http/filter_redirect.rb +10 -12
  67. data/lib/action_dispatch/http/headers.rb +55 -22
  68. data/lib/action_dispatch/http/mime_negotiation.rb +79 -51
  69. data/lib/action_dispatch/http/mime_type.rb +153 -121
  70. data/lib/action_dispatch/http/mime_types.rb +20 -6
  71. data/lib/action_dispatch/http/parameters.rb +90 -40
  72. data/lib/action_dispatch/http/permissions_policy.rb +173 -0
  73. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  74. data/lib/action_dispatch/http/request.rb +226 -121
  75. data/lib/action_dispatch/http/response.rb +248 -113
  76. data/lib/action_dispatch/http/upload.rb +21 -7
  77. data/lib/action_dispatch/http/url.rb +182 -100
  78. data/lib/action_dispatch/journey/formatter.rb +90 -43
  79. data/lib/action_dispatch/journey/gtg/builder.rb +28 -41
  80. data/lib/action_dispatch/journey/gtg/simulator.rb +11 -16
  81. data/lib/action_dispatch/journey/gtg/transition_table.rb +23 -21
  82. data/lib/action_dispatch/journey/nfa/dot.rb +3 -14
  83. data/lib/action_dispatch/journey/nodes/node.rb +29 -15
  84. data/lib/action_dispatch/journey/parser.rb +17 -16
  85. data/lib/action_dispatch/journey/parser.y +4 -3
  86. data/lib/action_dispatch/journey/parser_extras.rb +12 -4
  87. data/lib/action_dispatch/journey/path/pattern.rb +58 -54
  88. data/lib/action_dispatch/journey/route.rb +100 -32
  89. data/lib/action_dispatch/journey/router/utils.rb +29 -18
  90. data/lib/action_dispatch/journey/router.rb +55 -51
  91. data/lib/action_dispatch/journey/routes.rb +17 -17
  92. data/lib/action_dispatch/journey/scanner.rb +26 -17
  93. data/lib/action_dispatch/journey/visitors.rb +98 -54
  94. data/lib/action_dispatch/journey.rb +5 -5
  95. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  96. data/lib/action_dispatch/middleware/callbacks.rb +3 -6
  97. data/lib/action_dispatch/middleware/cookies.rb +347 -217
  98. data/lib/action_dispatch/middleware/debug_exceptions.rb +135 -63
  99. data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
  100. data/lib/action_dispatch/middleware/debug_view.rb +66 -0
  101. data/lib/action_dispatch/middleware/exception_wrapper.rb +115 -71
  102. data/lib/action_dispatch/middleware/executor.rb +21 -0
  103. data/lib/action_dispatch/middleware/flash.rb +78 -54
  104. data/lib/action_dispatch/middleware/host_authorization.rb +130 -0
  105. data/lib/action_dispatch/middleware/public_exceptions.rb +32 -27
  106. data/lib/action_dispatch/middleware/reloader.rb +5 -91
  107. data/lib/action_dispatch/middleware/remote_ip.rb +53 -45
  108. data/lib/action_dispatch/middleware/request_id.rb +17 -10
  109. data/lib/action_dispatch/middleware/session/abstract_store.rb +41 -26
  110. data/lib/action_dispatch/middleware/session/cache_store.rb +24 -14
  111. data/lib/action_dispatch/middleware/session/cookie_store.rb +74 -75
  112. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -2
  113. data/lib/action_dispatch/middleware/show_exceptions.rb +28 -23
  114. data/lib/action_dispatch/middleware/ssl.rb +118 -35
  115. data/lib/action_dispatch/middleware/stack.rb +82 -41
  116. data/lib/action_dispatch/middleware/static.rb +156 -89
  117. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  119. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +4 -14
  121. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  122. data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +4 -2
  123. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  124. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +45 -35
  125. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -0
  126. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +23 -4
  128. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
  129. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +15 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +105 -8
  132. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +2 -2
  135. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -1
  136. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +3 -3
  137. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  138. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.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 +87 -64
  141. data/lib/action_dispatch/railtie.rb +27 -13
  142. data/lib/action_dispatch/request/session.rb +109 -61
  143. data/lib/action_dispatch/request/utils.rb +90 -23
  144. data/lib/action_dispatch/routing/endpoint.rb +9 -2
  145. data/lib/action_dispatch/routing/inspector.rb +141 -102
  146. data/lib/action_dispatch/routing/mapper.rb +811 -473
  147. data/lib/action_dispatch/routing/polymorphic_routes.rb +167 -143
  148. data/lib/action_dispatch/routing/redirection.rb +37 -27
  149. data/lib/action_dispatch/routing/route_set.rb +363 -331
  150. data/lib/action_dispatch/routing/routes_proxy.rb +32 -5
  151. data/lib/action_dispatch/routing/url_for.rb +66 -26
  152. data/lib/action_dispatch/routing.rb +36 -36
  153. data/lib/action_dispatch/system_test_case.rb +190 -0
  154. data/lib/action_dispatch/system_testing/browser.rb +86 -0
  155. data/lib/action_dispatch/system_testing/driver.rb +67 -0
  156. data/lib/action_dispatch/system_testing/server.rb +31 -0
  157. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +138 -0
  158. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +29 -0
  159. data/lib/action_dispatch/testing/assertion_response.rb +46 -0
  160. data/lib/action_dispatch/testing/assertions/response.rb +44 -22
  161. data/lib/action_dispatch/testing/assertions/routing.rb +47 -31
  162. data/lib/action_dispatch/testing/assertions.rb +6 -4
  163. data/lib/action_dispatch/testing/integration.rb +391 -220
  164. data/lib/action_dispatch/testing/request_encoder.rb +55 -0
  165. data/lib/action_dispatch/testing/test_process.rb +53 -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 +35 -21
  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 +78 -48
  173. data/lib/action_controller/metal/force_ssl.rb +0 -97
  174. data/lib/action_controller/metal/hide_actions.rb +0 -40
  175. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  176. data/lib/action_controller/middleware.rb +0 -39
  177. data/lib/action_controller/model_naming.rb +0 -12
  178. data/lib/action_dispatch/http/parameter_filter.rb +0 -72
  179. data/lib/action_dispatch/journey/backwards.rb +0 -5
  180. data/lib/action_dispatch/journey/nfa/builder.rb +0 -76
  181. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
  182. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -163
  183. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  184. data/lib/action_dispatch/middleware/params_parser.rb +0 -60
  185. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  186. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  187. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -1,48 +1,77 @@
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
+ "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
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
+ 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
32
45
 
33
- def initialize(env, exception)
34
- @env = env
35
- @exception = original_exception(exception)
46
+ def initialize(backtrace_cleaner, exception)
47
+ @backtrace_cleaner = backtrace_cleaner
48
+ @exception = exception
49
+ @exception_class_name = @exception.class.name
50
+ @wrapped_causes = wrapped_causes_for(exception, backtrace_cleaner)
36
51
 
37
- expand_backtrace if exception.is_a?(SyntaxError) || exception.try(:original_exception).try(:is_a?, SyntaxError)
52
+ expand_backtrace if exception.is_a?(SyntaxError) || exception.cause.is_a?(SyntaxError)
53
+ end
54
+
55
+ def unwrapped_exception
56
+ if wrapper_exceptions.include?(@exception_class_name)
57
+ exception.cause
58
+ else
59
+ exception
60
+ end
38
61
  end
39
62
 
40
63
  def rescue_template
41
- @@rescue_templates[@exception.class.name]
64
+ @@rescue_templates[@exception_class_name]
42
65
  end
43
66
 
44
67
  def status_code
45
- 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
46
75
  end
47
76
 
48
77
  def application_trace
@@ -58,15 +87,19 @@ module ActionDispatch
58
87
  end
59
88
 
60
89
  def traces
61
- appplication_trace_with_ids = []
90
+ application_trace_with_ids = []
62
91
  framework_trace_with_ids = []
63
92
  full_trace_with_ids = []
64
93
 
65
94
  full_trace.each_with_index do |trace, idx|
66
- 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
+ }
67
100
 
68
101
  if application_trace.include?(trace)
69
- appplication_trace_with_ids << trace_with_id
102
+ application_trace_with_ids << trace_with_id
70
103
  else
71
104
  framework_trace_with_ids << trace_with_id
72
105
  end
@@ -75,7 +108,7 @@ module ActionDispatch
75
108
  end
76
109
 
77
110
  {
78
- "Application Trace" => appplication_trace_with_ids,
111
+ "Application Trace" => application_trace_with_ids,
79
112
  "Framework Trace" => framework_trace_with_ids,
80
113
  "Full Trace" => full_trace_with_ids
81
114
  }
@@ -87,8 +120,7 @@ module ActionDispatch
87
120
 
88
121
  def source_extracts
89
122
  backtrace.map do |trace|
90
- file, line = trace.split(":")
91
- line_number = line.to_i
123
+ file, line_number = extract_file_and_line_number(trace)
92
124
 
93
125
  {
94
126
  code: source_fragment(file, line_number),
@@ -97,52 +129,64 @@ module ActionDispatch
97
129
  end
98
130
  end
99
131
 
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
132
+ def trace_to_show
133
+ if traces["Application Trace"].empty? && rescue_template != "routing_error"
134
+ "Full Trace"
109
135
  else
110
- exception
136
+ "Application Trace"
111
137
  end
112
138
  end
113
139
 
114
- def registered_original_exception?(exception)
115
- exception.respond_to?(:original_exception) && @@rescue_responses.has_key?(exception.original_exception.class.name)
140
+ def source_to_show_id
141
+ (traces[trace_to_show].first || {})[:id]
116
142
  end
117
143
 
118
- def clean_backtrace(*args)
119
- if backtrace_cleaner
120
- backtrace_cleaner.clean(backtrace, *args)
121
- else
122
- backtrace
144
+ private
145
+ def backtrace
146
+ Array(@exception.backtrace)
123
147
  end
124
- end
125
148
 
126
- def backtrace_cleaner
127
- @backtrace_cleaner ||= @env['action_dispatch.backtrace_cleaner']
128
- 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) }
157
+ end
158
+
159
+ def clean_backtrace(*args)
160
+ if backtrace_cleaner
161
+ backtrace_cleaner.clean(backtrace, *args)
162
+ else
163
+ backtrace
164
+ end
165
+ end
129
166
 
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]
167
+ def source_fragment(path, line)
168
+ return unless Rails.respond_to?(:root) && Rails.root
169
+ full_path = Rails.root.join(path)
170
+ if File.exist?(full_path)
171
+ 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]
175
+ end
138
176
  end
139
177
  end
140
- end
141
178
 
142
- def expand_backtrace
143
- @exception.backtrace.unshift(
144
- @exception.to_s.split("\n")
145
- ).flatten!
146
- end
179
+ 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!
190
+ end
147
191
  end
148
192
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rack/body_proxy"
4
+
5
+ module ActionDispatch
6
+ class Executor
7
+ def initialize(app, executor)
8
+ @app, @executor = app, executor
9
+ end
10
+
11
+ def call(env)
12
+ state = @executor.run!
13
+ begin
14
+ response = @app.call(env)
15
+ returned = response << ::Rack::BodyProxy.new(response.pop) { state.complete! }
16
+ ensure
17
+ state.complete! unless returned
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,15 +1,8 @@
1
- require 'active_support/core_ext/hash/keys'
1
+ # frozen_string_literal: true
2
2
 
3
- module ActionDispatch
4
- class Request < Rack::Request
5
- # Access the contents of the flash. Use <tt>flash["notice"]</tt> to
6
- # read a notice you put there or <tt>flash["notice"] = "hello"</tt>
7
- # to put a new one.
8
- def flash
9
- @env[Flash::KEY] ||= Flash::FlashHash.from_session_value(session["flash"])
10
- end
11
- end
3
+ require "active_support/core_ext/hash/keys"
12
4
 
5
+ module ActionDispatch
13
6
  # The flash provides a way to pass temporary primitive-types (String, Array, Hash) between actions. Anything you place in the flash will be exposed
14
7
  # to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create
15
8
  # action that sets <tt>flash[:notice] = "Post successfully created"</tt> before redirecting to a display action that can
@@ -45,7 +38,46 @@ module ActionDispatch
45
38
  #
46
39
  # See docs on the FlashHash class for more details about the flash.
47
40
  class Flash
48
- KEY = 'action_dispatch.request.flash_hash'.freeze
41
+ KEY = "action_dispatch.request.flash_hash"
42
+
43
+ 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
+ def flash
48
+ flash = flash_hash
49
+ return flash if flash
50
+ self.flash = Flash::FlashHash.from_session_value(session["flash"])
51
+ end
52
+
53
+ def flash=(flash)
54
+ set_header Flash::KEY, flash
55
+ end
56
+
57
+ def flash_hash # :nodoc:
58
+ get_header Flash::KEY
59
+ end
60
+
61
+ def commit_flash # :nodoc:
62
+ session = self.session || {}
63
+ flash_hash = self.flash_hash
64
+
65
+ if flash_hash && (flash_hash.present? || session.key?("flash"))
66
+ session["flash"] = flash_hash.to_session_value
67
+ self.flash = flash_hash.dup
68
+ end
69
+
70
+ if (!session.respond_to?(:loaded?) || session.loaded?) && # reset_session uses {}, which doesn't implement #loaded?
71
+ session.key?("flash") && session["flash"].nil?
72
+ session.delete("flash")
73
+ end
74
+ end
75
+
76
+ def reset_session # :nodoc:
77
+ super
78
+ self.flash = nil
79
+ end
80
+ end
49
81
 
50
82
  class FlashNow #:nodoc:
51
83
  attr_accessor :flash
@@ -80,24 +112,30 @@ module ActionDispatch
80
112
  include Enumerable
81
113
 
82
114
  def self.from_session_value(value) #:nodoc:
83
- flash = case value
84
- when FlashHash # Rails 3.1, 3.2
85
- new(value.instance_variable_get(:@flashes), value.instance_variable_get(:@used))
86
- when Hash # Rails 4.0
87
- new(value['flashes'], value['discard'])
88
- else
89
- new
90
- end
91
-
92
- flash.tap(&:sweep)
93
- end
94
-
95
- # Builds a hash containing the discarded values and the hashes
96
- # representing the flashes.
97
- # If there are no values in @flashes, returns nil.
115
+ case value
116
+ when FlashHash # Rails 3.1, 3.2
117
+ flashes = value.instance_variable_get(:@flashes)
118
+ if discard = value.instance_variable_get(:@used)
119
+ flashes.except!(*discard)
120
+ end
121
+ new(flashes, flashes.keys)
122
+ when Hash # Rails 4.0
123
+ flashes = value["flashes"]
124
+ if discard = value["discard"]
125
+ flashes.except!(*discard)
126
+ end
127
+ new(flashes, flashes.keys)
128
+ else
129
+ new
130
+ end
131
+ end
132
+
133
+ # Builds a hash containing the flashes to keep for the next request.
134
+ # If there are none to keep, returns +nil+.
98
135
  def to_session_value #:nodoc:
99
- return nil if empty?
100
- {'discard' => @discard.to_a, 'flashes' => @flashes}
136
+ flashes_to_keep = @flashes.except(*@discard)
137
+ return nil if flashes_to_keep.empty?
138
+ { "discard" => [], "flashes" => flashes_to_keep }
101
139
  end
102
140
 
103
141
  def initialize(flashes = {}, discard = []) #:nodoc:
@@ -241,36 +279,22 @@ module ActionDispatch
241
279
  end
242
280
 
243
281
  protected
244
- def now_is_loaded?
245
- @now
246
- end
247
-
248
- def stringify_array(array)
249
- array.map do |item|
250
- item.kind_of?(Symbol) ? item.to_s : item
282
+ def now_is_loaded?
283
+ @now
251
284
  end
252
- end
253
- end
254
285
 
255
- def initialize(app)
256
- @app = app
286
+ private
287
+ def stringify_array(array) # :doc:
288
+ array.map do |item|
289
+ item.kind_of?(Symbol) ? item.to_s : item
290
+ end
291
+ end
257
292
  end
258
293
 
259
- def call(env)
260
- @app.call(env)
261
- ensure
262
- session = Request::Session.find(env) || {}
263
- flash_hash = env[KEY]
264
-
265
- if flash_hash && (flash_hash.present? || session.key?('flash'))
266
- session["flash"] = flash_hash.to_session_value
267
- env[KEY] = flash_hash.dup
268
- end
294
+ def self.new(app) app; end
295
+ end
269
296
 
270
- if (!session.respond_to?(:loaded?) || session.loaded?) && # (reset_session uses {}, which doesn't implement #loaded?)
271
- session.key?('flash') && session['flash'].nil?
272
- session.delete('flash')
273
- end
274
- end
297
+ class Request
298
+ prepend Flash::RequestMethods
275
299
  end
276
300
  end
@@ -0,0 +1,130 @@
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 <tt>403 Forbidden</tt>.
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#{Regexp.escape 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
+ valid_host = /
107
+ \A
108
+ (?<host>[a-z0-9.-]+|\[[a-f0-9]*:[a-f0-9.:]+\])
109
+ (:\d+)?
110
+ \z
111
+ /x
112
+
113
+ origin_host = valid_host.match(
114
+ request.get_header("HTTP_HOST").to_s.downcase)
115
+ forwarded_host = valid_host.match(
116
+ request.x_forwarded_host.to_s.split(/,\s?/).last)
117
+
118
+ origin_host && @permissions.allows?(origin_host[:host]) && (
119
+ forwarded_host.nil? || @permissions.allows?(forwarded_host[:host]))
120
+ end
121
+
122
+ def excluded?(request)
123
+ @exclude && @exclude.call(request)
124
+ end
125
+
126
+ def mark_as_authorized(request)
127
+ request.set_header("action_dispatch.authorized_host", request.host)
128
+ end
129
+ end
130
+ end
@@ -1,11 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionDispatch
2
4
  # When called, this middleware renders an error page. By default if an HTML
3
- # response is expected it will render static error pages from the `/public`
5
+ # response is expected it will render static error pages from the <tt>/public</tt>
4
6
  # directory. For example when this middleware receives a 500 response it will
5
- # render the template found in `/public/500.html`.
7
+ # render the template found in <tt>/public/500.html</tt>.
6
8
  # If an internationalized locale is set, this middleware will attempt to render
7
- # the template in `/public/500.<locale>.html`. If an internationalized template
8
- # is not found it will fall back on `/public/500.html`.
9
+ # the template in <tt>/public/500.<locale>.html</tt>. If an internationalized template
10
+ # is not found it will fall back on <tt>/public/500.html</tt>.
9
11
  #
10
12
  # When a request with a content type other than HTML is made, this middleware
11
13
  # will attempt to convert error information into the appropriate response type.
@@ -17,39 +19,42 @@ module ActionDispatch
17
19
  end
18
20
 
19
21
  def call(env)
20
- status = env["PATH_INFO"][1..-1]
21
22
  request = ActionDispatch::Request.new(env)
22
- content_type = request.formats.first
23
- body = { :status => status, :error => Rack::Utils::HTTP_STATUS_CODES.fetch(status.to_i, Rack::Utils::HTTP_STATUS_CODES[500]) }
23
+ status = request.path_info[1..-1].to_i
24
+ begin
25
+ content_type = request.formats.first
26
+ rescue ActionDispatch::Http::MimeNegotiation::InvalidType
27
+ content_type = Mime[:text]
28
+ end
29
+ body = { status: status, error: Rack::Utils::HTTP_STATUS_CODES.fetch(status, Rack::Utils::HTTP_STATUS_CODES[500]) }
24
30
 
25
31
  render(status, content_type, body)
26
32
  end
27
33
 
28
34
  private
29
-
30
- def render(status, content_type, body)
31
- format = "to_#{content_type.to_sym}" if content_type
32
- if format && body.respond_to?(format)
33
- render_format(status, content_type, body.public_send(format))
34
- else
35
- render_html(status)
35
+ def render(status, content_type, body)
36
+ format = "to_#{content_type.to_sym}" if content_type
37
+ if format && body.respond_to?(format)
38
+ render_format(status, content_type, body.public_send(format))
39
+ else
40
+ render_html(status)
41
+ end
36
42
  end
37
- end
38
43
 
39
- def render_format(status, content_type, body)
40
- [status, {'Content-Type' => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
41
- 'Content-Length' => body.bytesize.to_s}, [body]]
42
- end
44
+ def render_format(status, content_type, body)
45
+ [status, { "Content-Type" => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
46
+ "Content-Length" => body.bytesize.to_s }, [body]]
47
+ end
43
48
 
44
- def render_html(status)
45
- path = "#{public_path}/#{status}.#{I18n.locale}.html"
46
- path = "#{public_path}/#{status}.html" unless (found = File.exist?(path))
49
+ def render_html(status)
50
+ path = "#{public_path}/#{status}.#{I18n.locale}.html"
51
+ path = "#{public_path}/#{status}.html" unless (found = File.exist?(path))
47
52
 
48
- if found || File.exist?(path)
49
- render_format(status, 'text/html', File.read(path))
50
- else
51
- [404, { "X-Cascade" => "pass" }, []]
53
+ if found || File.exist?(path)
54
+ render_format(status, "text/html", File.read(path))
55
+ else
56
+ [404, { "X-Cascade" => "pass" }, []]
57
+ end
52
58
  end
53
- end
54
59
  end
55
60
  end