actionpack 6.1.4 → 7.0.0.rc1

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 (112) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +189 -372
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -3
  5. data/lib/abstract_controller/asset_paths.rb +1 -1
  6. data/lib/abstract_controller/base.rb +7 -21
  7. data/lib/abstract_controller/caching/fragments.rb +2 -2
  8. data/lib/abstract_controller/caching.rb +1 -1
  9. data/lib/abstract_controller/callbacks.rb +21 -7
  10. data/lib/abstract_controller/collector.rb +4 -2
  11. data/lib/abstract_controller/error.rb +1 -1
  12. data/lib/abstract_controller/helpers.rb +3 -2
  13. data/lib/abstract_controller/logger.rb +1 -1
  14. data/lib/abstract_controller/railties/routes_helpers.rb +2 -0
  15. data/lib/abstract_controller/translation.rb +3 -2
  16. data/lib/abstract_controller/url_for.rb +4 -6
  17. data/lib/action_controller/api.rb +1 -1
  18. data/lib/action_controller/log_subscriber.rb +4 -3
  19. data/lib/action_controller/metal/conditional_get.rb +38 -1
  20. data/lib/action_controller/metal/content_security_policy.rb +1 -1
  21. data/lib/action_controller/metal/cookies.rb +1 -1
  22. data/lib/action_controller/metal/data_streaming.rb +5 -13
  23. data/lib/action_controller/metal/exceptions.rb +19 -30
  24. data/lib/action_controller/metal/flash.rb +6 -2
  25. data/lib/action_controller/metal/helpers.rb +1 -1
  26. data/lib/action_controller/metal/http_authentication.rb +17 -16
  27. data/lib/action_controller/metal/instrumentation.rb +57 -52
  28. data/lib/action_controller/metal/live.rb +42 -2
  29. data/lib/action_controller/metal/mime_responds.rb +3 -3
  30. data/lib/action_controller/metal/params_wrapper.rb +20 -11
  31. data/lib/action_controller/metal/permissions_policy.rb +1 -1
  32. data/lib/action_controller/metal/redirecting.rb +86 -16
  33. data/lib/action_controller/metal/rendering.rb +7 -7
  34. data/lib/action_controller/metal/request_forgery_protection.rb +64 -24
  35. data/lib/action_controller/metal/rescue.rb +1 -1
  36. data/lib/action_controller/metal/streaming.rb +1 -3
  37. data/lib/action_controller/metal/strong_parameters.rb +84 -47
  38. data/lib/action_controller/metal/testing.rb +0 -2
  39. data/lib/action_controller/metal.rb +7 -10
  40. data/lib/action_controller/railtie.rb +49 -6
  41. data/lib/action_controller/test_case.rb +19 -4
  42. data/lib/action_controller.rb +1 -5
  43. data/lib/action_dispatch/http/cache.rb +13 -6
  44. data/lib/action_dispatch/http/content_security_policy.rb +39 -35
  45. data/lib/action_dispatch/http/filter_parameters.rb +5 -0
  46. data/lib/action_dispatch/http/mime_negotiation.rb +13 -3
  47. data/lib/action_dispatch/http/mime_type.rb +9 -11
  48. data/lib/action_dispatch/http/parameters.rb +4 -4
  49. data/lib/action_dispatch/http/permissions_policy.rb +1 -1
  50. data/lib/action_dispatch/http/request.rb +10 -19
  51. data/lib/action_dispatch/http/response.rb +1 -13
  52. data/lib/action_dispatch/http/url.rb +11 -19
  53. data/lib/action_dispatch/journey/gtg/builder.rb +11 -12
  54. data/lib/action_dispatch/journey/gtg/simulator.rb +10 -4
  55. data/lib/action_dispatch/journey/gtg/transition_table.rb +77 -21
  56. data/lib/action_dispatch/journey/nodes/node.rb +70 -5
  57. data/lib/action_dispatch/journey/path/pattern.rb +22 -13
  58. data/lib/action_dispatch/journey/route.rb +6 -13
  59. data/lib/action_dispatch/journey/router/utils.rb +2 -2
  60. data/lib/action_dispatch/journey/router.rb +1 -1
  61. data/lib/action_dispatch/journey/routes.rb +3 -3
  62. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  63. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  64. data/lib/action_dispatch/middleware/actionable_exceptions.rb +0 -1
  65. data/lib/action_dispatch/middleware/cookies.rb +8 -4
  66. data/lib/action_dispatch/middleware/debug_exceptions.rb +6 -4
  67. data/lib/action_dispatch/middleware/debug_locks.rb +3 -3
  68. data/lib/action_dispatch/middleware/exception_wrapper.rb +4 -0
  69. data/lib/action_dispatch/middleware/executor.rb +3 -0
  70. data/lib/action_dispatch/middleware/flash.rb +9 -11
  71. data/lib/action_dispatch/middleware/host_authorization.rb +49 -37
  72. data/lib/action_dispatch/middleware/remote_ip.rb +16 -4
  73. data/lib/action_dispatch/middleware/server_timing.rb +33 -0
  74. data/lib/action_dispatch/middleware/session/abstract_store.rb +1 -1
  75. data/lib/action_dispatch/middleware/show_exceptions.rb +17 -9
  76. data/lib/action_dispatch/middleware/stack.rb +27 -9
  77. data/lib/action_dispatch/middleware/static.rb +2 -6
  78. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +1 -1
  79. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +4 -11
  80. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +2 -2
  81. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +4 -3
  82. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +3 -1
  83. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +4 -4
  84. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +3 -3
  85. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +28 -18
  86. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +3 -3
  87. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +3 -3
  88. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
  89. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +3 -3
  90. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +3 -3
  91. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +5 -14
  92. data/lib/action_dispatch/railtie.rb +8 -2
  93. data/lib/action_dispatch/request/session.rb +43 -13
  94. data/lib/action_dispatch/routing/inspector.rb +1 -1
  95. data/lib/action_dispatch/routing/mapper.rb +54 -78
  96. data/lib/action_dispatch/routing/redirection.rb +0 -2
  97. data/lib/action_dispatch/routing/route_set.rb +14 -6
  98. data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
  99. data/lib/action_dispatch/routing/url_for.rb +1 -2
  100. data/lib/action_dispatch/routing.rb +2 -2
  101. data/lib/action_dispatch/system_test_case.rb +12 -6
  102. data/lib/action_dispatch/system_testing/browser.rb +2 -12
  103. data/lib/action_dispatch/system_testing/driver.rb +35 -11
  104. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +10 -6
  105. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +0 -8
  106. data/lib/action_dispatch/testing/assertions.rb +2 -5
  107. data/lib/action_dispatch/testing/integration.rb +6 -8
  108. data/lib/action_dispatch/testing/test_process.rb +3 -26
  109. data/lib/action_dispatch.rb +2 -1
  110. data/lib/action_pack/gem_version.rb +4 -4
  111. data/lib/action_pack.rb +1 -1
  112. metadata +19 -17
@@ -59,16 +59,14 @@ module ActionDispatch
59
59
  end
60
60
 
61
61
  def commit_flash # :nodoc:
62
- session = self.session || {}
63
- flash_hash = self.flash_hash
62
+ return unless session.enabled?
64
63
 
65
64
  if flash_hash && (flash_hash.present? || session.key?("flash"))
66
65
  session["flash"] = flash_hash.to_session_value
67
66
  self.flash = flash_hash.dup
68
67
  end
69
68
 
70
- if (!session.respond_to?(:loaded?) || session.loaded?) && # reset_session uses {}, which doesn't implement #loaded?
71
- session.key?("flash") && session["flash"].nil?
69
+ if session.loaded? && session.key?("flash") && session["flash"].nil?
72
70
  session.delete("flash")
73
71
  end
74
72
  end
@@ -79,7 +77,7 @@ module ActionDispatch
79
77
  end
80
78
  end
81
79
 
82
- class FlashNow #:nodoc:
80
+ class FlashNow # :nodoc:
83
81
  attr_accessor :flash
84
82
 
85
83
  def initialize(flash)
@@ -111,7 +109,7 @@ module ActionDispatch
111
109
  class FlashHash
112
110
  include Enumerable
113
111
 
114
- def self.from_session_value(value) #:nodoc:
112
+ def self.from_session_value(value) # :nodoc:
115
113
  case value
116
114
  when FlashHash # Rails 3.1, 3.2
117
115
  flashes = value.instance_variable_get(:@flashes)
@@ -132,13 +130,13 @@ module ActionDispatch
132
130
 
133
131
  # Builds a hash containing the flashes to keep for the next request.
134
132
  # If there are none to keep, returns +nil+.
135
- def to_session_value #:nodoc:
133
+ def to_session_value # :nodoc:
136
134
  flashes_to_keep = @flashes.except(*@discard)
137
135
  return nil if flashes_to_keep.empty?
138
136
  { "discard" => [], "flashes" => flashes_to_keep }
139
137
  end
140
138
 
141
- def initialize(flashes = {}, discard = []) #:nodoc:
139
+ def initialize(flashes = {}, discard = []) # :nodoc:
142
140
  @discard = Set.new(stringify_array(discard))
143
141
  @flashes = flashes.stringify_keys
144
142
  @now = nil
@@ -162,7 +160,7 @@ module ActionDispatch
162
160
  @flashes[k.to_s]
163
161
  end
164
162
 
165
- def update(h) #:nodoc:
163
+ def update(h) # :nodoc:
166
164
  @discard.subtract stringify_array(h.keys)
167
165
  @flashes.update h.stringify_keys
168
166
  self
@@ -202,7 +200,7 @@ module ActionDispatch
202
200
 
203
201
  alias :merge! :update
204
202
 
205
- def replace(h) #:nodoc:
203
+ def replace(h) # :nodoc:
206
204
  @discard.clear
207
205
  @flashes.replace h.stringify_keys
208
206
  self
@@ -253,7 +251,7 @@ module ActionDispatch
253
251
  # Mark for removal entries that were kept, and delete unkept ones.
254
252
  #
255
253
  # This method is called automatically by filters, so you generally don't need to care about it.
256
- def sweep #:nodoc:
254
+ def sweep # :nodoc:
257
255
  @discard.each { |k| @flashes.delete k }
258
256
  @discard.replace @flashes.keys
259
257
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "action_dispatch/http/request"
4
-
5
3
  module ActionDispatch
6
4
  # This middleware guards from DNS rebinding attacks by explicitly permitting
7
5
  # the hosts a request can be sent to, and is passed the options set in
@@ -13,7 +11,10 @@ module ActionDispatch
13
11
  #
14
12
  # When a request comes to an unauthorized host, the +response_app+
15
13
  # 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>.
14
+ # default one will run.
15
+ # The default response app logs blocked host info with level 'error' and
16
+ # responds with <tt>403 Forbidden</tt>. The body of the response contains debug info
17
+ # if +config.consider_all_requests_local+ is set to true, otherwise the body is empty.
17
18
  class HostAuthorization
18
19
  class Permissions # :nodoc:
19
20
  def initialize(hosts)
@@ -58,34 +59,51 @@ module ActionDispatch
58
59
  end
59
60
  end
60
61
 
61
- DEFAULT_RESPONSE_APP = -> env do
62
- request = Request.new(env)
62
+ class DefaultResponseApp # :nodoc:
63
+ RESPONSE_STATUS = 403
64
+
65
+ def call(env)
66
+ request = Request.new(env)
67
+ format = request.xhr? ? "text/plain" : "text/html"
68
+
69
+ log_error(request)
70
+ response(format, response_body(request))
71
+ end
72
+
73
+ private
74
+ def response_body(request)
75
+ return "" unless request.get_header("action_dispatch.show_detailed_exceptions")
76
+
77
+ template = DebugView.new(host: request.host)
78
+ template.render(template: "rescues/blocked_host", layout: "rescues/layout")
79
+ end
80
+
81
+ def response(format, body)
82
+ [RESPONSE_STATUS,
83
+ { "Content-Type" => "#{format}; charset=#{Response.default_charset}",
84
+ "Content-Length" => body.bytesize.to_s },
85
+ [body]]
86
+ end
87
+
88
+ def log_error(request)
89
+ logger = available_logger(request)
63
90
 
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")
91
+ return unless logger
67
92
 
68
- [403, {
69
- "Content-Type" => "#{format}; charset=#{Response.default_charset}",
70
- "Content-Length" => body.bytesize.to_s,
71
- }, [body]]
93
+ logger.error("[#{self.class.name}] Blocked host: #{request.host}")
94
+ end
95
+
96
+ def available_logger(request)
97
+ request.logger || ActionView::Base.logger
98
+ end
72
99
  end
73
100
 
74
- def initialize(app, hosts, deprecated_response_app = nil, exclude: nil, response_app: nil)
101
+ def initialize(app, hosts, exclude: nil, response_app: nil)
75
102
  @app = app
76
103
  @permissions = Permissions.new(hosts)
77
104
  @exclude = exclude
78
105
 
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
106
+ @response_app = response_app || DefaultResponseApp.new
89
107
  end
90
108
 
91
109
  def call(env)
@@ -102,21 +120,15 @@ module ActionDispatch
102
120
  end
103
121
 
104
122
  private
123
+ HOSTNAME = /[a-z0-9.-]+|\[[a-f0-9]*:[a-f0-9.:]+\]/i
124
+ VALID_ORIGIN_HOST = /\A(#{HOSTNAME})(?::\d+)?\z/
125
+ VALID_FORWARDED_HOST = /(?:\A|,[ ]?)(#{HOSTNAME})(?::\d+)?\z/
126
+
105
127
  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]))
128
+ origin_host = request.get_header("HTTP_HOST")&.slice(VALID_ORIGIN_HOST, 1) || ""
129
+ forwarded_host = request.x_forwarded_host&.slice(VALID_FORWARDED_HOST, 1) || ""
130
+
131
+ @permissions.allows?(origin_host) && (forwarded_host.blank? || @permissions.allows?(forwarded_host))
120
132
  end
121
133
 
122
134
  def excluded?(request)
@@ -51,10 +51,8 @@ module ActionDispatch
51
51
  # clients (like WAP devices), or behind proxies that set headers in an
52
52
  # incorrect or confusing way (like AWS ELB).
53
53
  #
54
- # The +custom_proxies+ argument can take an Array of string, IPAddr, or
55
- # Regexp objects which will be used instead of +TRUSTED_PROXIES+. If a
56
- # single string, IPAddr, or Regexp object is provided, it will be used in
57
- # addition to +TRUSTED_PROXIES+. Any proxy setup will put the value you
54
+ # The +custom_proxies+ argument can take an enumerable which will be used
55
+ # instead of +TRUSTED_PROXIES+. Any proxy setup will put the value you
58
56
  # want in the middle (or at the beginning) of the X-Forwarded-For list,
59
57
  # with your proxy servers after it. If your proxies aren't removed, pass
60
58
  # them in via the +custom_proxies+ parameter. That way, the middleware will
@@ -67,6 +65,20 @@ module ActionDispatch
67
65
  elsif custom_proxies.respond_to?(:any?)
68
66
  custom_proxies
69
67
  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
71
+ example, instead of:
72
+
73
+ config.action_dispatch.trusted_proxies = IPAddr.new("10.0.0.0/8")
74
+
75
+ Wrap the value in an Array:
76
+
77
+ config.action_dispatch.trusted_proxies = [IPAddr.new("10.0.0.0/8")]
78
+
79
+ Note that unlike passing a single argument, passing an enumerable
80
+ will *replace* the default set of trusted proxies.
81
+ EOM
70
82
  Array(custom_proxies) + TRUSTED_PROXIES
71
83
  end
72
84
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/notifications"
4
+
5
+ module ActionDispatch
6
+ class ServerTiming
7
+ SERVER_TIMING_HEADER = "Server-Timing"
8
+
9
+ def initialize(app)
10
+ @app = app
11
+ end
12
+
13
+ def call(env)
14
+ events = []
15
+ subscriber = ActiveSupport::Notifications.subscribe(/.*/) do |*args|
16
+ events << ActiveSupport::Notifications::Event.new(*args)
17
+ end
18
+
19
+ status, headers, body = begin
20
+ @app.call(env)
21
+ ensure
22
+ ActiveSupport::Notifications.unsubscribe(subscriber)
23
+ end
24
+
25
+ header_info = events.group_by(&:name).map do |event_name, events_collection|
26
+ "#{event_name};dur=#{events_collection.sum(&:duration)}"
27
+ end
28
+ headers[SERVER_TIMING_HEADER] = header_info.join(", ")
29
+
30
+ [ status, headers, body ]
31
+ end
32
+ end
33
+ end
@@ -8,7 +8,7 @@ require "action_dispatch/request/session"
8
8
 
9
9
  module ActionDispatch
10
10
  module Session
11
- class SessionRestoreError < StandardError #:nodoc:
11
+ class SessionRestoreError < StandardError # :nodoc:
12
12
  def initialize
13
13
  super("Session contains objects whose class definition isn't available.\n" \
14
14
  "Remember to require the classes for all objects kept in the session.\n" \
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "action_dispatch/http/request"
4
3
  require "action_dispatch/middleware/exception_wrapper"
5
4
 
6
5
  module ActionDispatch
@@ -15,14 +14,8 @@ module ActionDispatch
15
14
  # If the application returns a "X-Cascade" pass response, this middleware
16
15
  # will send an empty response as result with the correct status code.
17
16
  # If any exception happens inside the exceptions app, this middleware
18
- # catches the exceptions and returns a FAILSAFE_RESPONSE.
17
+ # catches the exceptions and returns a failsafe response.
19
18
  class ShowExceptions
20
- FAILSAFE_RESPONSE = [500, { "Content-Type" => "text/plain" },
21
- ["500 Internal Server Error\n" \
22
- "If you are the administrator of this website, then please read this web " \
23
- "application's log file and/or the web server's log file to find out what " \
24
- "went wrong."]]
25
-
26
19
  def initialize(app, exceptions_app)
27
20
  @app = app
28
21
  @exceptions_app = exceptions_app
@@ -47,13 +40,28 @@ module ActionDispatch
47
40
  request.set_header "action_dispatch.exception", wrapper.unwrapped_exception
48
41
  request.set_header "action_dispatch.original_path", request.path_info
49
42
  request.set_header "action_dispatch.original_request_method", request.raw_request_method
43
+ fallback_to_html_format_if_invalid_mime_type(request)
50
44
  request.path_info = "/#{status}"
51
45
  request.request_method = "GET"
52
46
  response = @exceptions_app.call(request.env)
53
47
  response[1]["X-Cascade"] == "pass" ? pass_response(status) : response
54
48
  rescue Exception => failsafe_error
55
49
  $stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
56
- FAILSAFE_RESPONSE
50
+
51
+ [500, { "Content-Type" => "text/plain" },
52
+ ["500 Internal Server Error\n" \
53
+ "If you are the administrator of this website, then please read this web " \
54
+ "application's log file and/or the web server's log file to find out what " \
55
+ "went wrong."]]
56
+ end
57
+
58
+ def fallback_to_html_format_if_invalid_mime_type(request)
59
+ # If the MIME type for the request is invalid then the
60
+ # @exceptions_app may not be able to handle it. To make it
61
+ # easier to handle, we switch to HTML.
62
+ request.formats
63
+ rescue ActionDispatch::Http::MimeNegotiation::InvalidType
64
+ request.set_header "HTTP_ACCEPT", "text/html"
57
65
  end
58
66
 
59
67
  def pass_response(status)
@@ -72,8 +72,8 @@ module ActionDispatch
72
72
  yield(self) if block_given?
73
73
  end
74
74
 
75
- def each
76
- @middlewares.each { |x| yield x }
75
+ def each(&block)
76
+ @middlewares.each(&block)
77
77
  end
78
78
 
79
79
  def size
@@ -91,7 +91,7 @@ module ActionDispatch
91
91
  def unshift(klass, *args, &block)
92
92
  middlewares.unshift(build_middleware(klass, args, block))
93
93
  end
94
- ruby2_keywords(:unshift) if respond_to?(:ruby2_keywords, true)
94
+ ruby2_keywords(:unshift)
95
95
 
96
96
  def initialize_copy(other)
97
97
  self.middlewares = other.middlewares.dup
@@ -101,7 +101,7 @@ module ActionDispatch
101
101
  index = assert_index(index, :before)
102
102
  middlewares.insert(index, build_middleware(klass, args, block))
103
103
  end
104
- ruby2_keywords(:insert) if respond_to?(:ruby2_keywords, true)
104
+ ruby2_keywords(:insert)
105
105
 
106
106
  alias_method :insert_before, :insert
107
107
 
@@ -109,17 +109,29 @@ module ActionDispatch
109
109
  index = assert_index(index, :after)
110
110
  insert(index + 1, *args, &block)
111
111
  end
112
- ruby2_keywords(:insert_after) if respond_to?(:ruby2_keywords, true)
112
+ ruby2_keywords(:insert_after)
113
113
 
114
114
  def swap(target, *args, &block)
115
115
  index = assert_index(target, :before)
116
116
  insert(index, *args, &block)
117
117
  middlewares.delete_at(index + 1)
118
118
  end
119
- ruby2_keywords(:swap) if respond_to?(:ruby2_keywords, true)
119
+ ruby2_keywords(:swap)
120
120
 
121
+ # Deletes a middleware from the middleware stack.
122
+ #
123
+ # Returns the array of middlewares not including the deleted item, or
124
+ # returns nil if the target is not found.
121
125
  def delete(target)
122
- middlewares.delete_if { |m| m.klass == target }
126
+ middlewares.reject! { |m| m.name == target.name }
127
+ end
128
+
129
+ # Deletes a middleware from the middleware stack.
130
+ #
131
+ # Returns the array of middlewares not including the deleted item, or
132
+ # raises +RuntimeError+ if the target is not found.
133
+ def delete!(target)
134
+ delete(target) || (raise "No such middleware to remove: #{target.inspect}")
123
135
  end
124
136
 
125
137
  def move(target, source)
@@ -143,7 +155,7 @@ module ActionDispatch
143
155
  def use(klass, *args, &block)
144
156
  middlewares.push(build_middleware(klass, args, block))
145
157
  end
146
- ruby2_keywords(:use) if respond_to?(:ruby2_keywords, true)
158
+ ruby2_keywords(:use)
147
159
 
148
160
  def build(app = nil, &block)
149
161
  instrumenting = ActiveSupport::Notifications.notifier.listening?(InstrumentationProxy::EVENT_NAME)
@@ -158,7 +170,7 @@ module ActionDispatch
158
170
 
159
171
  private
160
172
  def assert_index(index, where)
161
- i = index.is_a?(Integer) ? index : middlewares.index { |m| m.klass == index }
173
+ i = index.is_a?(Integer) ? index : index_of(index)
162
174
  raise "No such middleware to insert #{where}: #{index.inspect}" unless i
163
175
  i
164
176
  end
@@ -166,5 +178,11 @@ module ActionDispatch
166
178
  def build_middleware(klass, args, block)
167
179
  Middleware.new(klass, args, block)
168
180
  end
181
+
182
+ def index_of(klass)
183
+ middlewares.index do |m|
184
+ m.name == klass.name
185
+ end
186
+ end
169
187
  end
170
188
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rack/utils"
4
- require "active_support/core_ext/uri"
5
4
 
6
5
  module ActionDispatch
7
6
  # This middleware serves static files from disk, if available.
@@ -137,11 +136,8 @@ module ActionDispatch
137
136
  end
138
137
 
139
138
  def file_readable?(path)
140
- file_stat = File.stat(File.join(@root, path.b))
141
- rescue SystemCallError
142
- false
143
- else
144
- file_stat.file? && file_stat.readable?
139
+ file_path = File.join(@root, path.b)
140
+ File.file?(file_path) && File.readable?(file_path)
145
141
  end
146
142
 
147
143
  def compressible?(content_type)
@@ -11,7 +11,7 @@
11
11
  <b>Did you mean?</b>
12
12
  <ul>
13
13
  <% corrections.each do |correction| %>
14
- <li style="list-style-type: none"><%= h correction %></li>
14
+ <li class="correction"><%= h correction %></li>
15
15
  <% end %>
16
16
  </ul>
17
17
  <% end %>
@@ -1,24 +1,17 @@
1
- <% unless @exception.blamed_files.blank? %>
2
- <% if (hide = @exception.blamed_files.length > 8) %>
3
- <a href="#" onclick="return toggleTrace()">Toggle blamed files</a>
4
- <% end %>
5
- <pre id="blame_trace" <%='style="display:none"' if hide %>><code><%= @exception.describe_blame %></code></pre>
6
- <% end %>
7
-
8
- <h2 style="margin-top: 30px">Request</h2>
1
+ <h2 class="request-heading">Request</h2>
9
2
  <% if params_valid? %>
10
3
  <p><b>Parameters</b>:</p> <pre><%= debug_params(@request.filtered_parameters) %></pre>
11
4
  <% end %>
12
5
 
13
6
  <div class="details">
14
7
  <div class="summary"><a href="#" onclick="return toggleSessionDump()">Toggle session dump</a></div>
15
- <div id="session_dump" style="display:none"><pre><%= debug_hash @request.session %></pre></div>
8
+ <div id="session_dump" class="hidden"><pre><%= debug_hash @request.session %></pre></div>
16
9
  </div>
17
10
 
18
11
  <div class="details">
19
12
  <div class="summary"><a href="#" onclick="return toggleEnvDump()">Toggle env dump</a></div>
20
- <div id="env_dump" style="display:none"><pre><%= debug_hash @request.env.slice(*@request.class::ENV_METHODS) %></pre></div>
13
+ <div id="env_dump" class="hidden"><pre><%= debug_hash @request.env.slice(*@request.class::ENV_METHODS) %></pre></div>
21
14
  </div>
22
15
 
23
- <h2 style="margin-top: 30px">Response</h2>
16
+ <h2 class="response-heading">Response</h2>
24
17
  <p><b>Headers</b>:</p> <pre><%= debug_headers(defined?(@response) ? @response.headers : {}) %></pre>
@@ -14,7 +14,7 @@
14
14
 
15
15
  <% traces.each do |name, trace| %>
16
16
  <div id="<%= "#{name.gsub(/\s/, '-')}-#{error_index}" %>" style="display: <%= (name == trace_to_show) ? 'block' : 'none' %>;">
17
- <code style="font-size: 11px;">
17
+ <code class="traces">
18
18
  <% trace.each do |frame| %>
19
19
  <a class="trace-frames trace-frames-<%= error_index %>" data-exception-object-id="<%= frame[:exception_object_id] %>" data-frame-id="<%= frame[:id] %>" href="#">
20
20
  <%= frame[:trace] %>
@@ -25,7 +25,7 @@
25
25
  </div>
26
26
  <% end %>
27
27
 
28
- <script type="text/javascript">
28
+ <script>
29
29
  (function() {
30
30
  var traceFrames = document.getElementsByClassName('trace-frames-<%= error_index %>');
31
31
  var selectedFrame, currentSource = document.getElementById('frame-source-<%= error_index %>-0');
@@ -1,7 +1,8 @@
1
1
  <header>
2
2
  <h1>Blocked host: <%= @host %></h1>
3
3
  </header>
4
- <div id="container">
5
- <h2>To allow requests to <%= @host %>, add the following to your environment configuration:</h2>
4
+ <main role="main" id="container">
5
+ <h2>To allow requests to <%= @host %> make sure it is a valid hostname (containing only numbers, letters, dashes and dots), then add the following to your environment configuration:</h2>
6
6
  <pre>config.hosts &lt;&lt; "<%= @host %>"</pre>
7
- </div>
7
+ <p>For more details view: <a href="https://guides.rubyonrails.org/configuring.html#actiondispatch-hostauthorization">the Host Authorization guide</a></p>
8
+ </main>
@@ -1,5 +1,7 @@
1
1
  Blocked host: <%= @host %>
2
2
 
3
- To allow requests to <%= @host %>, add the following to your environment configuration:
3
+ To allow requests to <%= @host %> make sure it is a valid hostname (containing only numbers, letters, dashes and dots), then add the following to your environment configuration:
4
4
 
5
5
  config.hosts << "<%= @host %>"
6
+
7
+ For more details on host authorization view: https://guides.rubyonrails.org/configuring.html#actiondispatch-hostauthorization
@@ -7,7 +7,7 @@
7
7
  </h1>
8
8
  </header>
9
9
 
10
- <div id="container">
10
+ <main role="main" id="container">
11
11
  <%= render "rescues/message_and_suggestions", exception: @exception %>
12
12
  <%= render "rescues/actions", exception: @exception, request: @request %>
13
13
 
@@ -20,16 +20,16 @@
20
20
 
21
21
  <% @exception_wrapper.wrapped_causes.each.with_index(1) do |wrapper, index| %>
22
22
  <div class="details">
23
- <a class="summary" href="#" style="color: #F0F0F0; text-decoration: none; background: #C52F24; border-bottom: none;" onclick="return toggle(<%= wrapper.exception.object_id %>)">
23
+ <a class="summary" href="#" onclick="return toggle(<%= wrapper.exception.object_id %>)">
24
24
  <%= wrapper.exception.class.name %>: <%= h wrapper.exception.message %>
25
25
  </a>
26
26
  </div>
27
27
 
28
- <div id="<%= wrapper.exception.object_id %>" style="display: none;">
28
+ <div id="<%= wrapper.exception.object_id %>" class="hidden">
29
29
  <%= render "rescues/source", source_extracts: wrapper.source_extracts, show_source_idx: wrapper.source_to_show_id, error_index: index %>
30
30
  <%= render "rescues/trace", traces: wrapper.traces, trace_to_show: wrapper.trace_to_show, error_index: index %>
31
31
  </div>
32
32
  <% end %>
33
33
 
34
34
  <%= render template: "rescues/_request_and_response" %>
35
- </div>
35
+ </main>
@@ -1,4 +1,4 @@
1
- <header>
1
+ <header role="banner">
2
2
  <h1>
3
3
  <%= @exception.class.to_s %>
4
4
  <% if @request.parameters['controller'] %>
@@ -7,7 +7,7 @@
7
7
  </h1>
8
8
  </header>
9
9
 
10
- <div id="container">
10
+ <main role="main" id="container">
11
11
  <h2>
12
12
  <%= h @exception.message %>
13
13
  <% if defined?(ActiveStorage) && @exception.message.match?(%r{#{ActiveStorage::Blob.table_name}|#{ActiveStorage::Attachment.table_name}}) %>
@@ -21,4 +21,4 @@
21
21
  <%= render "rescues/source", source_extracts: @source_extracts, show_source_idx: @show_source_idx %>
22
22
  <%= render "rescues/trace", traces: @traces, trace_to_show: @trace_to_show %>
23
23
  <%= render template: "rescues/_request_and_response" %>
24
- </div>
24
+ </main>