actionpack 7.0.4 → 7.1.3.4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (140) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +397 -269
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -4
  5. data/lib/abstract_controller/base.rb +20 -11
  6. data/lib/abstract_controller/caching/fragments.rb +2 -0
  7. data/lib/abstract_controller/callbacks.rb +31 -6
  8. data/lib/abstract_controller/deprecator.rb +7 -0
  9. data/lib/abstract_controller/helpers.rb +75 -28
  10. data/lib/abstract_controller/railties/routes_helpers.rb +1 -16
  11. data/lib/abstract_controller/rendering.rb +12 -14
  12. data/lib/abstract_controller/translation.rb +9 -6
  13. data/lib/abstract_controller/url_for.rb +2 -0
  14. data/lib/abstract_controller.rb +6 -0
  15. data/lib/action_controller/api.rb +6 -4
  16. data/lib/action_controller/base.rb +3 -17
  17. data/lib/action_controller/caching.rb +2 -0
  18. data/lib/action_controller/deprecator.rb +7 -0
  19. data/lib/action_controller/form_builder.rb +2 -0
  20. data/lib/action_controller/log_subscriber.rb +16 -4
  21. data/lib/action_controller/metal/basic_implicit_render.rb +3 -1
  22. data/lib/action_controller/metal/conditional_get.rb +121 -123
  23. data/lib/action_controller/metal/content_security_policy.rb +5 -5
  24. data/lib/action_controller/metal/data_streaming.rb +20 -18
  25. data/lib/action_controller/metal/default_headers.rb +2 -0
  26. data/lib/action_controller/metal/etag_with_flash.rb +3 -1
  27. data/lib/action_controller/metal/etag_with_template_digest.rb +2 -0
  28. data/lib/action_controller/metal/exceptions.rb +8 -0
  29. data/lib/action_controller/metal/head.rb +9 -7
  30. data/lib/action_controller/metal/helpers.rb +3 -14
  31. data/lib/action_controller/metal/http_authentication.rb +17 -8
  32. data/lib/action_controller/metal/implicit_render.rb +5 -3
  33. data/lib/action_controller/metal/instrumentation.rb +8 -1
  34. data/lib/action_controller/metal/live.rb +25 -1
  35. data/lib/action_controller/metal/mime_responds.rb +2 -2
  36. data/lib/action_controller/metal/params_wrapper.rb +4 -2
  37. data/lib/action_controller/metal/permissions_policy.rb +2 -2
  38. data/lib/action_controller/metal/redirecting.rb +29 -8
  39. data/lib/action_controller/metal/renderers.rb +4 -4
  40. data/lib/action_controller/metal/rendering.rb +114 -9
  41. data/lib/action_controller/metal/request_forgery_protection.rb +144 -53
  42. data/lib/action_controller/metal/rescue.rb +6 -3
  43. data/lib/action_controller/metal/streaming.rb +71 -31
  44. data/lib/action_controller/metal/strong_parameters.rb +158 -101
  45. data/lib/action_controller/metal/url_for.rb +9 -4
  46. data/lib/action_controller/metal.rb +79 -21
  47. data/lib/action_controller/railtie.rb +24 -10
  48. data/lib/action_controller/renderer.rb +99 -85
  49. data/lib/action_controller/test_case.rb +15 -5
  50. data/lib/action_controller.rb +8 -1
  51. data/lib/action_dispatch/constants.rb +32 -0
  52. data/lib/action_dispatch/deprecator.rb +7 -0
  53. data/lib/action_dispatch/http/cache.rb +9 -11
  54. data/lib/action_dispatch/http/content_security_policy.rb +14 -9
  55. data/lib/action_dispatch/http/filter_parameters.rb +14 -28
  56. data/lib/action_dispatch/http/headers.rb +3 -1
  57. data/lib/action_dispatch/http/mime_negotiation.rb +22 -22
  58. data/lib/action_dispatch/http/mime_type.rb +35 -12
  59. data/lib/action_dispatch/http/mime_types.rb +3 -1
  60. data/lib/action_dispatch/http/parameters.rb +1 -1
  61. data/lib/action_dispatch/http/permissions_policy.rb +38 -23
  62. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  63. data/lib/action_dispatch/http/request.rb +63 -30
  64. data/lib/action_dispatch/http/response.rb +80 -63
  65. data/lib/action_dispatch/http/upload.rb +15 -2
  66. data/lib/action_dispatch/journey/formatter.rb +8 -2
  67. data/lib/action_dispatch/journey/path/pattern.rb +14 -14
  68. data/lib/action_dispatch/journey/route.rb +3 -2
  69. data/lib/action_dispatch/journey/router.rb +9 -8
  70. data/lib/action_dispatch/journey/routes.rb +2 -2
  71. data/lib/action_dispatch/log_subscriber.rb +23 -0
  72. data/lib/action_dispatch/middleware/actionable_exceptions.rb +5 -6
  73. data/lib/action_dispatch/middleware/assume_ssl.rb +24 -0
  74. data/lib/action_dispatch/middleware/callbacks.rb +2 -0
  75. data/lib/action_dispatch/middleware/cookies.rb +108 -117
  76. data/lib/action_dispatch/middleware/debug_exceptions.rb +26 -25
  77. data/lib/action_dispatch/middleware/debug_locks.rb +4 -1
  78. data/lib/action_dispatch/middleware/debug_view.rb +7 -2
  79. data/lib/action_dispatch/middleware/exception_wrapper.rb +186 -27
  80. data/lib/action_dispatch/middleware/executor.rb +1 -1
  81. data/lib/action_dispatch/middleware/flash.rb +7 -0
  82. data/lib/action_dispatch/middleware/host_authorization.rb +18 -8
  83. data/lib/action_dispatch/middleware/public_exceptions.rb +5 -3
  84. data/lib/action_dispatch/middleware/reloader.rb +7 -5
  85. data/lib/action_dispatch/middleware/remote_ip.rb +21 -20
  86. data/lib/action_dispatch/middleware/request_id.rb +4 -2
  87. data/lib/action_dispatch/middleware/server_timing.rb +4 -4
  88. data/lib/action_dispatch/middleware/session/abstract_store.rb +5 -0
  89. data/lib/action_dispatch/middleware/session/cache_store.rb +2 -0
  90. data/lib/action_dispatch/middleware/session/cookie_store.rb +11 -5
  91. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +3 -1
  92. data/lib/action_dispatch/middleware/show_exceptions.rb +25 -18
  93. data/lib/action_dispatch/middleware/ssl.rb +18 -6
  94. data/lib/action_dispatch/middleware/stack.rb +7 -2
  95. data/lib/action_dispatch/middleware/static.rb +14 -10
  96. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
  97. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +4 -4
  98. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
  99. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -3
  100. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -3
  101. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +7 -7
  102. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
  103. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +17 -0
  104. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +16 -12
  105. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -1
  106. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
  107. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +4 -4
  108. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  109. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  110. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +3 -0
  111. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +59 -41
  112. data/lib/action_dispatch/railtie.rb +14 -4
  113. data/lib/action_dispatch/request/session.rb +16 -6
  114. data/lib/action_dispatch/request/utils.rb +8 -3
  115. data/lib/action_dispatch/routing/inspector.rb +54 -6
  116. data/lib/action_dispatch/routing/mapper.rb +58 -24
  117. data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -0
  118. data/lib/action_dispatch/routing/redirection.rb +15 -6
  119. data/lib/action_dispatch/routing/route_set.rb +52 -22
  120. data/lib/action_dispatch/routing/routes_proxy.rb +10 -15
  121. data/lib/action_dispatch/routing/url_for.rb +26 -22
  122. data/lib/action_dispatch/routing.rb +7 -7
  123. data/lib/action_dispatch/system_test_case.rb +3 -3
  124. data/lib/action_dispatch/system_testing/browser.rb +20 -19
  125. data/lib/action_dispatch/system_testing/driver.rb +14 -22
  126. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +27 -16
  127. data/lib/action_dispatch/testing/assertion_response.rb +1 -1
  128. data/lib/action_dispatch/testing/assertions/response.rb +14 -7
  129. data/lib/action_dispatch/testing/assertions/routing.rb +67 -28
  130. data/lib/action_dispatch/testing/assertions.rb +3 -1
  131. data/lib/action_dispatch/testing/integration.rb +27 -17
  132. data/lib/action_dispatch/testing/request_encoder.rb +4 -1
  133. data/lib/action_dispatch/testing/test_process.rb +4 -3
  134. data/lib/action_dispatch/testing/test_request.rb +1 -1
  135. data/lib/action_dispatch/testing/test_response.rb +23 -9
  136. data/lib/action_dispatch.rb +37 -4
  137. data/lib/action_pack/gem_version.rb +4 -4
  138. data/lib/action_pack/version.rb +1 -1
  139. data/lib/action_pack.rb +1 -1
  140. metadata +65 -29
@@ -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
51
62
 
52
- expand_backtrace if exception.is_a?(SyntaxError) || exception.cause.is_a?(SyntaxError)
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
98
+
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
106
+ else
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
58
114
  else
59
- exception
115
+ []
60
116
  end
61
117
  end
62
118
 
@@ -118,21 +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
+
121
200
  def rescue_response?
122
201
  @@rescue_responses.key?(exception.class.name)
123
202
  end
124
203
 
125
204
  def source_extracts
126
205
  backtrace.map do |trace|
127
- file, line_number = extract_file_and_line_number(trace)
128
-
129
- {
130
- code: source_fragment(file, line_number),
131
- line_number: line_number
132
- }
206
+ extract_source(trace)
133
207
  end
134
208
  end
135
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
+
136
215
  def trace_to_show
137
216
  if traces["Application Trace"].empty? && rescue_template != "routing_error"
138
217
  "Full Trace"
@@ -145,9 +224,65 @@ module ActionDispatch
145
224
  (traces[trace_to_show].first || {})[:id]
146
225
  end
147
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
+
148
243
  private
149
- def backtrace
150
- 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
151
286
  end
152
287
 
153
288
  def causes_for(exception)
@@ -168,29 +303,53 @@ module ActionDispatch
168
303
  end
169
304
  end
170
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
+
171
341
  def source_fragment(path, line)
172
342
  return unless Rails.respond_to?(:root) && Rails.root
173
343
  full_path = Rails.root.join(path)
174
344
  if File.exist?(full_path)
175
345
  File.open(full_path, "r") do |file|
176
- start = [line - 3, 0].max
177
- lines = file.each_line.drop(start).take(6)
178
- Hash[*(start + 1..(lines.count + start)).zip(lines).flatten]
346
+ extract_source_fragment_lines(file.each_line, line)
179
347
  end
180
348
  end
181
349
  end
182
350
 
183
351
  def extract_file_and_line_number(trace)
184
- # Split by the first colon followed by some digits, which works for both
185
- # Windows and Unix path styles.
186
- file, line = trace.match(/^(.+?):(\d+).*$/, &:captures) || trace
187
- [file, line.to_i]
188
- end
189
-
190
- def expand_backtrace
191
- @exception.backtrace.unshift(
192
- @exception.to_s.split("\n")
193
- ).flatten!
352
+ [trace.path, trace.lineno]
194
353
  end
195
354
  end
196
355
  end
@@ -14,7 +14,7 @@ module ActionDispatch
14
14
  response = @app.call(env)
15
15
  returned = response << ::Rack::BodyProxy.new(response.pop) { state.complete! }
16
16
  rescue => error
17
- @executor.error_reporter.report(error, handled: false)
17
+ @executor.error_reporter.report(error, handled: false, source: "application.action_dispatch")
18
18
  raise
19
19
  ensure
20
20
  state.complete! unless returned
@@ -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
@@ -175,6 +177,8 @@ module ActionDispatch
175
177
  @flashes.key? name.to_s
176
178
  end
177
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.
178
182
  def delete(key)
179
183
  key = key.to_s
180
184
  @discard.delete key
@@ -243,6 +247,9 @@ module ActionDispatch
243
247
  #
244
248
  # flash.discard # discard the entire flash at the end of the current action
245
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.
246
253
  def discard(k = nil)
247
254
  k = k.to_s if k
248
255
  @discard.merge Array(k || keys)
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionDispatch
4
+ # = Action Dispatch \HostAuthorization
5
+ #
4
6
  # This middleware guards from DNS rebinding attacks by explicitly permitting
5
7
  # the hosts a request can be sent to, and is passed the options set in
6
8
  # +config.host_authorization+.
@@ -18,6 +20,7 @@ module ActionDispatch
18
20
  class HostAuthorization
19
21
  ALLOWED_HOSTS_IN_DEVELOPMENT = [".localhost", IPAddr.new("0.0.0.0/0"), IPAddr.new("::/0")]
20
22
  PORT_REGEX = /(?::\d+)/ # :nodoc:
23
+ SUBDOMAIN_REGEX = /(?:[a-z0-9-]+\.)/i # :nodoc:
21
24
  IPV4_HOSTNAME = /(?<host>\d+\.\d+\.\d+\.\d+)#{PORT_REGEX}?/ # :nodoc:
22
25
  IPV6_HOSTNAME = /(?<host>[a-f0-9]*:[a-f0-9.:]+)/i # :nodoc:
23
26
  IPV6_HOSTNAME_WITH_PORT = /\[#{IPV6_HOSTNAME}\]#{PORT_REGEX}/i # :nodoc:
@@ -69,7 +72,7 @@ module ActionDispatch
69
72
 
70
73
  def sanitize_string(host)
71
74
  if host.start_with?(".")
72
- /\A([a-z0-9-]+\.)?#{Regexp.escape(host[1..-1])}#{PORT_REGEX}?\z/i
75
+ /\A#{SUBDOMAIN_REGEX}?#{Regexp.escape(host[1..-1])}#{PORT_REGEX}?\z/i
73
76
  else
74
77
  /\A#{Regexp.escape host}#{PORT_REGEX}?\z/i
75
78
  end
@@ -95,14 +98,14 @@ module ActionDispatch
95
98
  def response_body(request)
96
99
  return "" unless request.get_header("action_dispatch.show_detailed_exceptions")
97
100
 
98
- template = DebugView.new(host: request.host)
101
+ template = DebugView.new(hosts: request.env["action_dispatch.blocked_hosts"])
99
102
  template.render(template: "rescues/blocked_host", layout: "rescues/layout")
100
103
  end
101
104
 
102
105
  def response(format, body)
103
106
  [RESPONSE_STATUS,
104
- { "Content-Type" => "#{format}; charset=#{Response.default_charset}",
105
- "Content-Length" => body.bytesize.to_s },
107
+ { Rack::CONTENT_TYPE => "#{format}; charset=#{Response.default_charset}",
108
+ Rack::CONTENT_LENGTH => body.bytesize.to_s },
106
109
  [body]]
107
110
  end
108
111
 
@@ -111,7 +114,7 @@ module ActionDispatch
111
114
 
112
115
  return unless logger
113
116
 
114
- logger.error("[#{self.class.name}] Blocked host: #{request.host}")
117
+ logger.error("[#{self.class.name}] Blocked hosts: #{request.env["action_dispatch.blocked_hosts"].join(", ")}")
115
118
  end
116
119
 
117
120
  def available_logger(request)
@@ -131,21 +134,28 @@ module ActionDispatch
131
134
  return @app.call(env) if @permissions.empty?
132
135
 
133
136
  request = Request.new(env)
137
+ hosts = blocked_hosts(request)
134
138
 
135
- if authorized?(request) || excluded?(request)
139
+ if hosts.empty? || excluded?(request)
136
140
  mark_as_authorized(request)
137
141
  @app.call(env)
138
142
  else
143
+ env["action_dispatch.blocked_hosts"] = hosts
139
144
  @response_app.call(env)
140
145
  end
141
146
  end
142
147
 
143
148
  private
144
- def authorized?(request)
149
+ def blocked_hosts(request)
150
+ hosts = []
151
+
145
152
  origin_host = request.get_header("HTTP_HOST")
153
+ hosts << origin_host unless @permissions.allows?(origin_host)
154
+
146
155
  forwarded_host = request.x_forwarded_host&.split(/,\s?/)&.last
156
+ hosts << forwarded_host unless forwarded_host.blank? || @permissions.allows?(forwarded_host)
147
157
 
148
- @permissions.allows?(origin_host) && (forwarded_host.blank? || @permissions.allows?(forwarded_host))
158
+ hosts
149
159
  end
150
160
 
151
161
  def excluded?(request)
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionDispatch
4
+ # = Action Dispatch \PublicExceptions
5
+ #
4
6
  # When called, this middleware renders an error page. By default if an HTML
5
7
  # response is expected it will render static error pages from the <tt>/public</tt>
6
8
  # directory. For example when this middleware receives a 500 response it will
@@ -42,8 +44,8 @@ module ActionDispatch
42
44
  end
43
45
 
44
46
  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
+ [status, { Rack::CONTENT_TYPE => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
48
+ Rack::CONTENT_LENGTH => body.bytesize.to_s }, [body]]
47
49
  end
48
50
 
49
51
  def render_html(status)
@@ -53,7 +55,7 @@ module ActionDispatch
53
55
  if found || File.exist?(path)
54
56
  render_format(status, "text/html", File.read(path))
55
57
  else
56
- [404, { "X-Cascade" => "pass" }, []]
58
+ [404, { Constants::X_CASCADE => "pass" }, []]
57
59
  end
58
60
  end
59
61
  end
@@ -1,12 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionDispatch
4
- # ActionDispatch::Reloader wraps the request with callbacks provided by ActiveSupport::Reloader
5
- # callbacks, intended to assist with code reloading during development.
4
+ # = Action Dispatch \Reloader
6
5
  #
7
- # By default, ActionDispatch::Reloader is included in the middleware stack
8
- # only in the development environment; specifically, when +config.cache_classes+
9
- # is false.
6
+ # ActionDispatch::Reloader wraps the request with callbacks provided by
7
+ # ActiveSupport::Reloader, intended to assist with code reloading during
8
+ # development.
9
+ #
10
+ # ActionDispatch::Reloader is included in the middleware stack only if
11
+ # reloading is enabled, which it is by the default in +development+ mode.
10
12
  class Reloader < Executor
11
13
  end
12
14
  end
@@ -3,14 +3,14 @@
3
3
  require "ipaddr"
4
4
 
5
5
  module ActionDispatch
6
+ # = Action Dispatch \RemoteIp
7
+ #
6
8
  # This middleware calculates the IP address of the remote client that is
7
9
  # making the request. It does this by checking various headers that could
8
10
  # contain the address, and then picking the last-set address that is not
9
11
  # on the list of trusted IPs. This follows the precedent set by e.g.
10
- # {the Tomcat server}[https://issues.apache.org/bugzilla/show_bug.cgi?id=50453],
11
- # with {reasoning explained at length}[https://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection]
12
- # by @gingerlime. A more detailed explanation of the algorithm is given
13
- # at GetIp#calculate_ip.
12
+ # {the Tomcat server}[https://issues.apache.org/bugzilla/show_bug.cgi?id=50453].
13
+ # A more detailed explanation of the algorithm is given at GetIp#calculate_ip.
14
14
  #
15
15
  # Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2]
16
16
  # requires. Some Rack servers simply drop preceding headers, and only report
@@ -22,9 +22,10 @@ module ActionDispatch
22
22
  # This middleware assumes that there is at least one proxy sitting around
23
23
  # and setting headers with the client's remote IP address. If you don't use
24
24
  # a proxy, because you are hosted on e.g. Heroku without SSL, any client can
25
- # claim to have any IP address by setting the X-Forwarded-For header. If you
25
+ # claim to have any IP address by setting the +X-Forwarded-For+ header. If you
26
26
  # care about that, then you need to explicitly drop or ignore those headers
27
- # sometime before this middleware runs.
27
+ # sometime before this middleware runs. Alternatively, remove this middleware
28
+ # to avoid inadvertently relying on it.
28
29
  class RemoteIp
29
30
  class IpSpoofAttackError < StandardError; end
30
31
 
@@ -53,7 +54,7 @@ module ActionDispatch
53
54
  #
54
55
  # The +custom_proxies+ argument can take an enumerable which will be used
55
56
  # instead of +TRUSTED_PROXIES+. Any proxy setup will put the value you
56
- # want in the middle (or at the beginning) of the X-Forwarded-For list,
57
+ # want in the middle (or at the beginning) of the +X-Forwarded-For+ list,
57
58
  # with your proxy servers after it. If your proxies aren't removed, pass
58
59
  # them in via the +custom_proxies+ parameter. That way, the middleware will
59
60
  # ignore those IP addresses, and return the one that you want.
@@ -65,9 +66,9 @@ module ActionDispatch
65
66
  elsif custom_proxies.respond_to?(:any?)
66
67
  custom_proxies
67
68
  else
68
- ActiveSupport::Deprecation.warn(<<~EOM)
69
- Setting config.action_dispatch.trusted_proxies to a single value has
70
- been deprecated. Please set this to an enumerable instead. For
69
+ raise(ArgumentError, <<~EOM)
70
+ Setting config.action_dispatch.trusted_proxies to a single value isn't
71
+ supported. Please set this to an enumerable instead. For
71
72
  example, instead of:
72
73
 
73
74
  config.action_dispatch.trusted_proxies = IPAddr.new("10.0.0.0/8")
@@ -76,10 +77,8 @@ module ActionDispatch
76
77
 
77
78
  config.action_dispatch.trusted_proxies = [IPAddr.new("10.0.0.0/8")]
78
79
 
79
- Note that unlike passing a single argument, passing an enumerable
80
- will *replace* the default set of trusted proxies.
80
+ Note that passing an enumerable will *replace* the default set of trusted proxies.
81
81
  EOM
82
- Array(custom_proxies) + TRUSTED_PROXIES
83
82
  end
84
83
  end
85
84
 
@@ -110,11 +109,11 @@ module ActionDispatch
110
109
  # REMOTE_ADDR will be correct if the request is made directly against the
111
110
  # Ruby process, on e.g. Heroku. When the request is proxied by another
112
111
  # server like HAProxy or NGINX, the IP address that made the original
113
- # request will be put in an X-Forwarded-For header. If there are multiple
112
+ # request will be put in an +X-Forwarded-For+ header. If there are multiple
114
113
  # proxies, that header may contain a list of IPs. Other proxy services
115
- # set the Client-Ip header instead, so we check that too.
114
+ # set the +Client-Ip+ header instead, so we check that too.
116
115
  #
117
- # As discussed in {this post about Rails IP Spoofing}[https://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/],
116
+ # As discussed in {this post about Rails IP Spoofing}[https://web.archive.org/web/20170626095448/https://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/],
118
117
  # while the first IP in the list is likely to be the "originating" IP,
119
118
  # it could also have been set by the client maliciously.
120
119
  #
@@ -126,8 +125,8 @@ module ActionDispatch
126
125
  remote_addr = ips_from(@req.remote_addr).last
127
126
 
128
127
  # Could be a CSV list and/or repeated headers that were concatenated.
129
- client_ips = ips_from(@req.client_ip).reverse
130
- forwarded_ips = ips_from(@req.x_forwarded_for).reverse
128
+ client_ips = ips_from(@req.client_ip).reverse!
129
+ forwarded_ips = ips_from(@req.x_forwarded_for).reverse!
131
130
 
132
131
  # +Client-Ip+ and +X-Forwarded-For+ should not, generally, both be set.
133
132
  # If they are both set, it means that either:
@@ -155,7 +154,8 @@ module ActionDispatch
155
154
  # - X-Forwarded-For will be a list of IPs, one per proxy, or blank
156
155
  # - Client-Ip is propagated from the outermost proxy, or is blank
157
156
  # - REMOTE_ADDR will be the IP that made the request to Rack
158
- ips = [forwarded_ips, client_ips].flatten.compact
157
+ ips = forwarded_ips + client_ips
158
+ ips.compact!
159
159
 
160
160
  # If every single IP option is in the trusted list, return the IP
161
161
  # that's furthest away
@@ -173,7 +173,7 @@ module ActionDispatch
173
173
  return [] unless header
174
174
  # Split the comma-separated list into an array of strings.
175
175
  ips = header.strip.split(/[,\s]+/)
176
- ips.select do |ip|
176
+ ips.select! do |ip|
177
177
  # Only return IPs that are valid according to the IPAddr#new method.
178
178
  range = IPAddr.new(ip).to_range
179
179
  # We want to make sure nobody is sneaking a netmask in.
@@ -181,6 +181,7 @@ module ActionDispatch
181
181
  rescue ArgumentError
182
182
  nil
183
183
  end
184
+ ips
184
185
  end
185
186
 
186
187
  def filter_proxies(ips) # :doc:
@@ -4,11 +4,13 @@ require "securerandom"
4
4
  require "active_support/core_ext/string/access"
5
5
 
6
6
  module ActionDispatch
7
+ # = Action Dispatch \RequestId
8
+ #
7
9
  # Makes a unique request id available to the +action_dispatch.request_id+ env variable (which is then accessible
8
10
  # through ActionDispatch::Request#request_id or the alias ActionDispatch::Request#uuid) and sends
9
- # the same id to the client via the X-Request-Id header.
11
+ # the same id to the client via the +X-Request-Id+ header.
10
12
  #
11
- # The unique request id is either based on the X-Request-Id header in the request, which would typically be generated
13
+ # The unique request id is either based on the +X-Request-Id+ header in the request, which would typically be generated
12
14
  # by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the
13
15
  # header is accepted from the outside world, we sanitize it to a max of 255 chars and alphanumeric and dashes only.
14
16
  #
@@ -4,8 +4,6 @@ require "active_support/notifications"
4
4
 
5
5
  module ActionDispatch
6
6
  class ServerTiming
7
- SERVER_TIMING_HEADER = "Server-Timing"
8
-
9
7
  class Subscriber # :nodoc:
10
8
  include Singleton
11
9
  KEY = :action_dispatch_server_timing_events
@@ -67,8 +65,10 @@ module ActionDispatch
67
65
  "%s;dur=%.2f" % [event_name, events_collection.sum(&:duration)]
68
66
  end
69
67
 
70
- header_info.prepend(headers[SERVER_TIMING_HEADER]) if headers[SERVER_TIMING_HEADER].present?
71
- headers[SERVER_TIMING_HEADER] = header_info.join(", ")
68
+ if headers[ActionDispatch::Constants::SERVER_TIMING].present?
69
+ header_info.prepend(headers[ActionDispatch::Constants::SERVER_TIMING])
70
+ end
71
+ headers[ActionDispatch::Constants::SERVER_TIMING] = header_info.join(", ")
72
72
 
73
73
  response
74
74
  end
@@ -67,6 +67,11 @@ module ActionDispatch
67
67
  end
68
68
 
69
69
  module SessionObject # :nodoc:
70
+ def commit_session(req, res)
71
+ req.commit_csrf_token
72
+ super(req, res)
73
+ end
74
+
70
75
  def prepare_session(req)
71
76
  Request::Session.create(self, req, @default_options)
72
77
  end
@@ -4,6 +4,8 @@ require "action_dispatch/middleware/session/abstract_store"
4
4
 
5
5
  module ActionDispatch
6
6
  module Session
7
+ # = Action Dispatch Session \CacheStore
8
+ #
7
9
  # A session store that uses an ActiveSupport::Cache::Store to store the sessions. This store is most useful
8
10
  # if you don't store critical data in your sessions and you don't need them to live for extended periods
9
11
  # of time.
@@ -6,7 +6,9 @@ require "rack/session/cookie"
6
6
 
7
7
  module ActionDispatch
8
8
  module Session
9
- # This cookie-based session store is the Rails default. It is
9
+ # = Action Dispatch Session \CookieStore
10
+ #
11
+ # This cookie-based session store is the \Rails default. It is
10
12
  # dramatically faster than the alternatives.
11
13
  #
12
14
  # Sessions typically contain at most a user ID and flash message; both fit
@@ -18,18 +20,18 @@ module ActionDispatch
18
20
  #
19
21
  # Your cookies will be encrypted using your application's +secret_key_base+. This
20
22
  # goes a step further than signed cookies in that encrypted cookies cannot
21
- # be altered or read by users. This is the default starting in Rails 4.
23
+ # be altered or read by users. This is the default starting in \Rails 4.
22
24
  #
23
25
  # Configure your session store in an initializer:
24
26
  #
25
27
  # Rails.application.config.session_store :cookie_store, key: '_your_app_session'
26
28
  #
27
29
  # In the development and test environments your application's +secret_key_base+ is
28
- # generated by Rails and stored in a temporary file in <tt>tmp/development_secret.txt</tt>.
30
+ # generated by \Rails and stored in a temporary file in <tt>tmp/local_secret.txt</tt>.
29
31
  # In all other environments, it is stored encrypted in the
30
32
  # <tt>config/credentials.yml.enc</tt> file.
31
33
  #
32
- # If your application was not updated to Rails 5.2 defaults, the +secret_key_base+
34
+ # If your application was not updated to \Rails 5.2 defaults, the +secret_key_base+
33
35
  # will be found in the old <tt>config/secrets.yml</tt> file.
34
36
  #
35
37
  # Note that changing your +secret_key_base+ will invalidate all existing session.
@@ -56,8 +58,12 @@ module ActionDispatch
56
58
  end
57
59
  end
58
60
 
61
+ DEFAULT_SAME_SITE = proc { |request| request.cookies_same_site_protection } # :nodoc:
62
+
59
63
  def initialize(app, options = {})
60
- super(app, options.merge!(cookie_only: true))
64
+ options[:cookie_only] = true
65
+ options[:same_site] = DEFAULT_SAME_SITE if !options.key?(:same_site)
66
+ super
61
67
  end
62
68
 
63
69
  def delete_session(req, session_id, options)