actionpack 7.0.4 → 7.1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +495 -257
  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 +11 -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 +15 -9
  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 +200 -103
  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 +18 -8
  50. data/lib/action_controller.rb +13 -3
  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 +35 -13
  55. data/lib/action_dispatch/http/filter_parameters.rb +23 -32
  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 +37 -11
  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 +85 -32
  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 +7 -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 +39 -22
  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 +13 -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 +97 -26
  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 +53 -23
  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 +25 -19
  125. data/lib/action_dispatch/system_testing/driver.rb +15 -23
  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 +41 -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 +68 -32
@@ -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)
@@ -4,12 +4,14 @@ require "action_dispatch/middleware/session/abstract_store"
4
4
  begin
5
5
  require "rack/session/dalli"
6
6
  rescue LoadError => e
7
- $stderr.puts "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install"
7
+ warn "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install"
8
8
  raise e
9
9
  end
10
10
 
11
11
  module ActionDispatch
12
12
  module Session
13
+ # = Action Dispatch Session \MemCacheStore
14
+ #
13
15
  # A session store that uses MemCache to implement storage.
14
16
  #
15
17
  # ==== Options
@@ -3,18 +3,24 @@
3
3
  require "action_dispatch/middleware/exception_wrapper"
4
4
 
5
5
  module ActionDispatch
6
+ # = Action Dispatch \ShowExceptions
7
+ #
6
8
  # This middleware rescues any exception returned by the application
7
9
  # and calls an exceptions app that will wrap it in a format for the end user.
8
10
  #
9
- # The exceptions app should be passed as parameter on initialization
10
- # of ShowExceptions. Every time there is an exception, ShowExceptions will
11
- # store the exception in env["action_dispatch.exception"], rewrite the
12
- # PATH_INFO to the exception status code and call the Rack app.
11
+ # The exceptions app should be passed as a parameter on initialization of
12
+ # +ShowExceptions+. Every time there is an exception, +ShowExceptions+ will
13
+ # store the exception in <tt>env["action_dispatch.exception"]</tt>, rewrite
14
+ # the +PATH_INFO+ to the exception status code, and call the Rack app.
15
+ #
16
+ # In \Rails applications, the exceptions app can be configured with
17
+ # +config.exceptions_app+, which defaults to ActionDispatch::PublicExceptions.
13
18
  #
14
- # If the application returns a "X-Cascade" pass response, this middleware
15
- # will send an empty response as result with the correct status code.
16
- # If any exception happens inside the exceptions app, this middleware
17
- # catches the exceptions and returns a failsafe response.
19
+ # If the application returns a response with the <tt>X-Cascade</tt> header
20
+ # set to <tt>"pass"</tt>, this middleware will send an empty response as a
21
+ # result with the correct status code. If any exception happens inside the
22
+ # exceptions app, this middleware catches the exceptions and returns a
23
+ # failsafe response.
18
24
  class ShowExceptions
19
25
  def initialize(app, exceptions_app)
20
26
  @app = app
@@ -22,33 +28,35 @@ module ActionDispatch
22
28
  end
23
29
 
24
30
  def call(env)
25
- request = ActionDispatch::Request.new env
26
31
  @app.call(env)
27
32
  rescue Exception => exception
28
- if request.show_exceptions?
29
- render_exception(request, exception)
33
+ request = ActionDispatch::Request.new env
34
+ backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
35
+ wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
36
+ request.set_header "action_dispatch.exception", wrapper.unwrapped_exception
37
+ request.set_header "action_dispatch.report_exception", !wrapper.rescue_response?
38
+
39
+ if wrapper.show?(request)
40
+ render_exception(request.dup, wrapper)
30
41
  else
31
42
  raise exception
32
43
  end
33
44
  end
34
45
 
35
46
  private
36
- def render_exception(request, exception)
37
- backtrace_cleaner = request.get_header "action_dispatch.backtrace_cleaner"
38
- wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
39
- status = wrapper.status_code
40
- request.set_header "action_dispatch.exception", wrapper.unwrapped_exception
47
+ def render_exception(request, wrapper)
48
+ status = wrapper.status_code
41
49
  request.set_header "action_dispatch.original_path", request.path_info
42
50
  request.set_header "action_dispatch.original_request_method", request.raw_request_method
43
51
  fallback_to_html_format_if_invalid_mime_type(request)
44
52
  request.path_info = "/#{status}"
45
53
  request.request_method = "GET"
46
54
  response = @exceptions_app.call(request.env)
47
- response[1]["X-Cascade"] == "pass" ? pass_response(status) : response
55
+ response[1][Constants::X_CASCADE] == "pass" ? pass_response(status) : response
48
56
  rescue Exception => failsafe_error
49
57
  $stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
50
58
 
51
- [500, { "Content-Type" => "text/plain" },
59
+ [500, { Rack::CONTENT_TYPE => "text/plain; charset=utf-8" },
52
60
  ["500 Internal Server Error\n" \
53
61
  "If you are the administrator of this website, then please read this web " \
54
62
  "application's log file and/or the web server's log file to find out what " \
@@ -59,13 +67,22 @@ module ActionDispatch
59
67
  # If the MIME type for the request is invalid then the
60
68
  # @exceptions_app may not be able to handle it. To make it
61
69
  # easier to handle, we switch to HTML.
62
- request.formats
63
- rescue ActionDispatch::Http::MimeNegotiation::InvalidType
64
- request.set_header "HTTP_ACCEPT", "text/html"
70
+ begin
71
+ request.content_mime_type
72
+ rescue ActionDispatch::Http::MimeNegotiation::InvalidType
73
+ request.set_header "CONTENT_TYPE", "text/html"
74
+ end
75
+
76
+ begin
77
+ request.formats
78
+ rescue ActionDispatch::Http::MimeNegotiation::InvalidType
79
+ request.set_header "HTTP_ACCEPT", "text/html"
80
+ end
65
81
  end
66
82
 
67
83
  def pass_response(status)
68
- [status, { "Content-Type" => "text/html; charset=#{Response.default_charset}", "Content-Length" => "0" }, []]
84
+ [status, { Rack::CONTENT_TYPE => "text/html; charset=#{Response.default_charset}",
85
+ Rack::CONTENT_LENGTH => "0" }, []]
69
86
  end
70
87
  end
71
88
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionDispatch
4
+ # = Action Dispatch \SSL
5
+ #
4
6
  # This middleware is added to the stack when <tt>config.force_ssl = true</tt>, and is passed
5
7
  # the options set in +config.ssl_options+. It does three jobs to enforce secure HTTP
6
8
  # requests:
@@ -86,7 +88,7 @@ module ActionDispatch
86
88
 
87
89
  private
88
90
  def set_hsts_header!(headers)
89
- headers["Strict-Transport-Security"] ||= @hsts_header
91
+ headers[Constants::STRICT_TRANSPORT_SECURITY] ||= @hsts_header
90
92
  end
91
93
 
92
94
  def normalize_hsts_options(options)
@@ -112,23 +114,33 @@ module ActionDispatch
112
114
  end
113
115
 
114
116
  def flag_cookies_as_secure!(headers)
115
- if cookies = headers["Set-Cookie"]
116
- cookies = cookies.split("\n")
117
+ cookies = headers[Rack::SET_COOKIE]
118
+ return unless cookies
117
119
 
118
- headers["Set-Cookie"] = cookies.map { |cookie|
120
+ if Gem::Version.new(Rack::RELEASE) < Gem::Version.new("3")
121
+ cookies = cookies.split("\n")
122
+ headers[Rack::SET_COOKIE] = cookies.map { |cookie|
119
123
  if !/;\s*secure\s*(;|$)/i.match?(cookie)
120
124
  "#{cookie}; secure"
121
125
  else
122
126
  cookie
123
127
  end
124
128
  }.join("\n")
129
+ else
130
+ headers[Rack::SET_COOKIE] = Array(cookies).map do |cookie|
131
+ if !/;\s*secure\s*(;|$)/i.match?(cookie)
132
+ "#{cookie}; secure"
133
+ else
134
+ cookie
135
+ end
136
+ end
125
137
  end
126
138
  end
127
139
 
128
140
  def redirect_to_https(request)
129
141
  [ @redirect.fetch(:status, redirection_status(request)),
130
- { "Content-Type" => "text/html",
131
- "Location" => https_location_for(request) },
142
+ { Rack::CONTENT_TYPE => "text/html; charset=utf-8",
143
+ Constants::LOCATION => https_location_for(request) },
132
144
  (@redirect[:body] || []) ]
133
145
  end
134
146
 
@@ -4,6 +4,11 @@ require "active_support/inflector/methods"
4
4
  require "active_support/dependencies"
5
5
 
6
6
  module ActionDispatch
7
+ # = Action Dispatch \MiddlewareStack
8
+ #
9
+ # Read more about {Rails middleware
10
+ # stack}[https://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack]
11
+ # in the guides.
7
12
  class MiddlewareStack
8
13
  class Middleware
9
14
  attr_reader :args, :block, :klass
@@ -20,13 +25,13 @@ module ActionDispatch
20
25
  case middleware
21
26
  when Middleware
22
27
  klass == middleware.klass
23
- when Class
28
+ when Module
24
29
  klass == middleware
25
30
  end
26
31
  end
27
32
 
28
33
  def inspect
29
- if klass.is_a?(Class)
34
+ if klass.is_a?(Module)
30
35
  klass.to_s
31
36
  else
32
37
  klass.class.to_s
@@ -3,10 +3,12 @@
3
3
  require "rack/utils"
4
4
 
5
5
  module ActionDispatch
6
+ # = Action Dispatch \Static
7
+ #
6
8
  # This middleware serves static files from disk, if available.
7
9
  # If no file is found, it hands off to the main app.
8
10
  #
9
- # In Rails apps, this middleware is configured to serve assets from
11
+ # In \Rails apps, this middleware is configured to serve assets from
10
12
  # the +public/+ directory.
11
13
  #
12
14
  # Only GET and HEAD requests are served. POST and other HTTP methods
@@ -24,22 +26,24 @@ module ActionDispatch
24
26
  end
25
27
  end
26
28
 
27
- # This endpoint serves static files from disk using Rack::File.
29
+ # = Action Dispatch \FileHandler
30
+ #
31
+ # This endpoint serves static files from disk using +Rack::Files+.
28
32
  #
29
33
  # URL paths are matched with static files according to expected
30
34
  # conventions: +path+, +path+.html, +path+/index.html.
31
35
  #
32
36
  # Precompressed versions of these files are checked first. Brotli (.br)
33
37
  # and gzip (.gz) files are supported. If +path+.br exists, this
34
- # endpoint returns that file with a <tt>Content-Encoding: br</tt> header.
38
+ # endpoint returns that file with a <tt>content-encoding: br</tt> header.
35
39
  #
36
- # If no matching file is found, this endpoint responds 404 Not Found.
40
+ # If no matching file is found, this endpoint responds <tt>404 Not Found</tt>.
37
41
  #
38
42
  # Pass the +root+ directory to search for matching files, an optional
39
43
  # <tt>index: "index"</tt> to change the default +path+/index.html, and optional
40
44
  # additional response headers.
41
45
  class FileHandler
42
- # Accept-Encoding value -> file extension
46
+ # +Accept-Encoding+ value -> file extension
43
47
  PRECOMPRESSED = {
44
48
  "br" => ".br",
45
49
  "gzip" => ".gz",
@@ -53,7 +57,7 @@ module ActionDispatch
53
57
  @precompressed = Array(precompressed).map(&:to_s) | %w[ identity ]
54
58
  @compressible_content_types = compressible_content_types
55
59
 
56
- @file_server = ::Rack::File.new(@root, headers)
60
+ @file_server = ::Rack::Files.new(@root, headers)
57
61
  end
58
62
 
59
63
  def call(env)
@@ -76,7 +80,7 @@ module ActionDispatch
76
80
  request.path_info, ::Rack::Utils.escape_path(filepath).b
77
81
 
78
82
  @file_server.call(request.env).tap do |status, headers, body|
79
- # Omit Content-Encoding/Type/etc headers for 304 Not Modified
83
+ # Omit content-encoding/type/etc headers for 304 Not Modified
80
84
  if status != 304
81
85
  headers.update(content_headers)
82
86
  end
@@ -104,7 +108,7 @@ module ActionDispatch
104
108
  end
105
109
 
106
110
  def try_files(filepath, content_type, accept_encoding:)
107
- headers = { "Content-Type" => content_type }
111
+ headers = { Rack::CONTENT_TYPE => content_type }
108
112
 
109
113
  if compressible? content_type
110
114
  try_precompressed_files filepath, headers, accept_encoding: accept_encoding
@@ -124,10 +128,10 @@ module ActionDispatch
124
128
  if content_encoding == "identity"
125
129
  return precompressed_filepath, headers
126
130
  else
127
- headers["Vary"] = "Accept-Encoding"
131
+ headers[ActionDispatch::Constants::VARY] = "accept-encoding"
128
132
 
129
133
  if accept_encoding.any? { |enc, _| /\b#{content_encoding}\b/i.match?(enc) }
130
- headers["Content-Encoding"] = content_encoding
134
+ headers[ActionDispatch::Constants::CONTENT_ENCODING] = content_encoding
131
135
  return precompressed_filepath, headers
132
136
  end
133
137
  end
@@ -1,10 +1,10 @@
1
- <% actions = ActiveSupport::ActionableError.actions(exception) %>
1
+ <% actions = exception_wrapper.actions %>
2
2
 
3
3
  <% if actions.any? %>
4
4
  <div class="actions">
5
5
  <% actions.each do |action, _| %>
6
6
  <%= button_to action, ActionDispatch::ActionableExceptions.endpoint, params: {
7
- error: exception.class.name,
7
+ error: exception_wrapper.exception_class_name,
8
8
  action: action,
9
9
  location: request.path
10
10
  } %>
@@ -1,11 +1,11 @@
1
- <% if exception.respond_to?(:original_message) && exception.respond_to?(:corrections) %>
1
+ <% if exception_wrapper.has_corrections? %>
2
2
  <div class="exception-message">
3
- <%= simple_format h(exception.original_message), { class: "message" }, wrapper_tag: "div" %>
3
+ <%= simple_format h(exception_wrapper.original_message), { class: "message" }, wrapper_tag: "div" %>
4
4
  </div>
5
5
  <%
6
6
  # The 'did_you_mean' gem can raise exceptions when calling #corrections on
7
7
  # the exception. If it does there are no corrections to show.
8
- corrections = exception.corrections rescue []
8
+ corrections = exception_wrapper.corrections rescue []
9
9
  %>
10
10
  <% if corrections.any? %>
11
11
  <b>Did you mean?</b>
@@ -17,6 +17,6 @@
17
17
  <% end %>
18
18
  <% else %>
19
19
  <div class="exception-message">
20
- <%= simple_format h(exception.message), { class: "message" }, wrapper_tag: "div" %>
20
+ <%= simple_format h(exception_wrapper.message), { class: "message" }, wrapper_tag: "div" %>
21
21
  </div>
22
22
  <% end %>
@@ -18,12 +18,19 @@
18
18
  </td>
19
19
  <td width="100%">
20
20
  <pre>
21
- <% source_extract[:code].each do |line, source| -%><div class="line<%= " active" if line == source_extract[:line_number] -%>"><%= source -%></div><% end -%>
21
+ <% source_extract[:code].each do |line, source| -%>
22
+ <div class="line<%= " active" if line == source_extract[:line_number] -%>"><% if source.is_a?(Array) -%><%= source[0] -%><span class="error_highlight"><%= source[1] -%></span><%= source[2] -%>
23
+ <% else -%>
24
+ <%= source -%>
25
+ <% end -%></div><% end -%>
22
26
  </pre>
23
27
  </td>
24
28
  </tr>
25
29
  </table>
26
30
  </div>
31
+ <%- unless self.error_highlight_available? -%>
32
+ <p class="error_highlight_tip">Tip: You may want to add <code>gem 'error_highlight', '&gt;= 0.4.0'</code> into your Gemfile, which will display the fine-grained error location.</p>
33
+ <%- end -%>
27
34
  </div>
28
35
  <% end %>
29
36
  <% end %>
@@ -1,8 +1,12 @@
1
1
  <header>
2
- <h1>Blocked host: <%= @host %></h1>
2
+ <h1>Blocked hosts: <%= @hosts.join(", ") %></h1>
3
3
  </header>
4
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
- <pre>config.hosts &lt;&lt; "<%= @host %>"</pre>
5
+ <h2>To allow requests to these hosts, make sure they are valid hostnames (containing only numbers, letters, dashes and dots), then add the following to your environment configuration:</h2>
6
+ <pre>
7
+ <% @hosts.each do |host| %>
8
+ config.hosts &lt;&lt; "<%= host %>"
9
+ <% end %>
10
+ </pre>
7
11
  <p>For more details view: <a href="https://guides.rubyonrails.org/configuring.html#actiondispatch-hostauthorization">the Host Authorization guide</a></p>
8
12
  </main>
@@ -1,7 +1,9 @@
1
- Blocked host: <%= @host %>
1
+ Blocked hosts: <%= @hosts.join(", ") %>
2
2
 
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:
3
+ To allow requests to these hosts, make sure they are valid hostnames (containing only numbers, letters, dashes and dots), then add the following to your environment configuration:
4
4
 
5
- config.hosts << "<%= @host %>"
5
+ <% @hosts.each do |host| %>
6
+ config.hosts << "<%= host %>"
7
+ <% end %>
6
8
 
7
9
  For more details on host authorization view: https://guides.rubyonrails.org/configuring.html#actiondispatch-hostauthorization
@@ -1,6 +1,6 @@
1
1
  <header>
2
2
  <h1>
3
- <%= @exception.class.to_s %>
3
+ <%= @exception_wrapper.exception_class_name %>
4
4
  <% if params_valid? && @request.parameters['controller'] %>
5
5
  in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %>
6
6
  <% end %>
@@ -8,24 +8,24 @@
8
8
  </header>
9
9
 
10
10
  <main role="main" id="container">
11
- <%= render "rescues/message_and_suggestions", exception: @exception %>
12
- <%= render "rescues/actions", exception: @exception, request: @request %>
11
+ <%= render "rescues/message_and_suggestions", exception: @exception, exception_wrapper: @exception_wrapper %>
12
+ <%= render "rescues/actions", exception: @exception, request: @request, exception_wrapper: @exception_wrapper %>
13
13
 
14
14
  <%= render "rescues/source", source_extracts: @source_extracts, show_source_idx: @show_source_idx, error_index: 0 %>
15
15
  <%= render "rescues/trace", traces: @traces, trace_to_show: @trace_to_show, error_index: 0 %>
16
16
 
17
- <% if @exception.cause %>
17
+ <% if @exception_wrapper.has_cause? %>
18
18
  <h2>Exception Causes</h2>
19
19
  <% end %>
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="#" onclick="return toggle(<%= wrapper.exception.object_id %>)">
24
- <%= wrapper.exception.class.name %>: <%= h wrapper.exception.message %>
23
+ <a class="summary" href="#" onclick="return toggle(<%= wrapper.exception_id %>)">
24
+ <%= wrapper.exception_class_name %>: <%= h wrapper.message %>
25
25
  </a>
26
26
  </div>
27
27
 
28
- <div id="<%= wrapper.exception.object_id %>" class="hidden">
28
+ <div id="<%= wrapper.exception_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>