actionpack 7.0.8.7 → 7.2.2.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 (171) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +90 -537
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -2
  5. data/lib/abstract_controller/asset_paths.rb +2 -0
  6. data/lib/abstract_controller/base.rb +119 -106
  7. data/lib/abstract_controller/caching/fragments.rb +51 -52
  8. data/lib/abstract_controller/caching.rb +2 -0
  9. data/lib/abstract_controller/callbacks.rb +94 -67
  10. data/lib/abstract_controller/collector.rb +6 -6
  11. data/lib/abstract_controller/deprecator.rb +9 -0
  12. data/lib/abstract_controller/error.rb +2 -0
  13. data/lib/abstract_controller/helpers.rb +121 -91
  14. data/lib/abstract_controller/logger.rb +2 -0
  15. data/lib/abstract_controller/railties/routes_helpers.rb +3 -16
  16. data/lib/abstract_controller/rendering.rb +14 -13
  17. data/lib/abstract_controller/translation.rb +12 -30
  18. data/lib/abstract_controller/url_for.rb +9 -5
  19. data/lib/abstract_controller.rb +8 -0
  20. data/lib/action_controller/api/api_rendering.rb +2 -0
  21. data/lib/action_controller/api.rb +78 -73
  22. data/lib/action_controller/base.rb +199 -141
  23. data/lib/action_controller/caching.rb +16 -11
  24. data/lib/action_controller/deprecator.rb +9 -0
  25. data/lib/action_controller/form_builder.rb +21 -16
  26. data/lib/action_controller/log_subscriber.rb +19 -5
  27. data/lib/action_controller/metal/allow_browser.rb +123 -0
  28. data/lib/action_controller/metal/basic_implicit_render.rb +2 -0
  29. data/lib/action_controller/metal/conditional_get.rb +187 -174
  30. data/lib/action_controller/metal/content_security_policy.rb +26 -25
  31. data/lib/action_controller/metal/cookies.rb +4 -2
  32. data/lib/action_controller/metal/data_streaming.rb +65 -54
  33. data/lib/action_controller/metal/default_headers.rb +6 -2
  34. data/lib/action_controller/metal/etag_with_flash.rb +4 -0
  35. data/lib/action_controller/metal/etag_with_template_digest.rb +18 -14
  36. data/lib/action_controller/metal/exceptions.rb +19 -9
  37. data/lib/action_controller/metal/flash.rb +12 -10
  38. data/lib/action_controller/metal/head.rb +20 -16
  39. data/lib/action_controller/metal/helpers.rb +64 -67
  40. data/lib/action_controller/metal/http_authentication.rb +212 -199
  41. data/lib/action_controller/metal/implicit_render.rb +21 -17
  42. data/lib/action_controller/metal/instrumentation.rb +22 -12
  43. data/lib/action_controller/metal/live.rb +125 -92
  44. data/lib/action_controller/metal/logging.rb +6 -4
  45. data/lib/action_controller/metal/mime_responds.rb +151 -142
  46. data/lib/action_controller/metal/parameter_encoding.rb +34 -32
  47. data/lib/action_controller/metal/params_wrapper.rb +58 -58
  48. data/lib/action_controller/metal/permissions_policy.rb +14 -13
  49. data/lib/action_controller/metal/rate_limiting.rb +62 -0
  50. data/lib/action_controller/metal/redirecting.rb +110 -84
  51. data/lib/action_controller/metal/renderers.rb +50 -49
  52. data/lib/action_controller/metal/rendering.rb +103 -82
  53. data/lib/action_controller/metal/request_forgery_protection.rb +279 -161
  54. data/lib/action_controller/metal/rescue.rb +12 -8
  55. data/lib/action_controller/metal/streaming.rb +174 -132
  56. data/lib/action_controller/metal/strong_parameters.rb +598 -473
  57. data/lib/action_controller/metal/testing.rb +2 -0
  58. data/lib/action_controller/metal/url_for.rb +23 -14
  59. data/lib/action_controller/metal.rb +145 -61
  60. data/lib/action_controller/railtie.rb +25 -9
  61. data/lib/action_controller/railties/helpers.rb +2 -0
  62. data/lib/action_controller/renderer.rb +105 -66
  63. data/lib/action_controller/template_assertions.rb +4 -2
  64. data/lib/action_controller/test_case.rb +157 -128
  65. data/lib/action_controller.rb +17 -3
  66. data/lib/action_dispatch/constants.rb +34 -0
  67. data/lib/action_dispatch/deprecator.rb +9 -0
  68. data/lib/action_dispatch/http/cache.rb +28 -29
  69. data/lib/action_dispatch/http/content_disposition.rb +2 -0
  70. data/lib/action_dispatch/http/content_security_policy.rb +48 -45
  71. data/lib/action_dispatch/http/filter_parameters.rb +18 -8
  72. data/lib/action_dispatch/http/filter_redirect.rb +22 -1
  73. data/lib/action_dispatch/http/headers.rb +23 -21
  74. data/lib/action_dispatch/http/mime_negotiation.rb +37 -48
  75. data/lib/action_dispatch/http/mime_type.rb +60 -30
  76. data/lib/action_dispatch/http/mime_types.rb +5 -1
  77. data/lib/action_dispatch/http/parameters.rb +12 -10
  78. data/lib/action_dispatch/http/permissions_policy.rb +32 -27
  79. data/lib/action_dispatch/http/rack_cache.rb +4 -0
  80. data/lib/action_dispatch/http/request.rb +132 -79
  81. data/lib/action_dispatch/http/response.rb +136 -103
  82. data/lib/action_dispatch/http/upload.rb +19 -15
  83. data/lib/action_dispatch/http/url.rb +75 -73
  84. data/lib/action_dispatch/journey/formatter.rb +19 -6
  85. data/lib/action_dispatch/journey/gtg/builder.rb +4 -3
  86. data/lib/action_dispatch/journey/gtg/simulator.rb +2 -0
  87. data/lib/action_dispatch/journey/gtg/transition_table.rb +10 -8
  88. data/lib/action_dispatch/journey/nfa/dot.rb +2 -0
  89. data/lib/action_dispatch/journey/nodes/node.rb +6 -5
  90. data/lib/action_dispatch/journey/parser.rb +4 -3
  91. data/lib/action_dispatch/journey/parser_extras.rb +2 -0
  92. data/lib/action_dispatch/journey/path/pattern.rb +18 -15
  93. data/lib/action_dispatch/journey/route.rb +12 -9
  94. data/lib/action_dispatch/journey/router/utils.rb +16 -15
  95. data/lib/action_dispatch/journey/router.rb +13 -10
  96. data/lib/action_dispatch/journey/routes.rb +6 -4
  97. data/lib/action_dispatch/journey/scanner.rb +4 -2
  98. data/lib/action_dispatch/journey/visitors.rb +2 -0
  99. data/lib/action_dispatch/journey.rb +2 -0
  100. data/lib/action_dispatch/log_subscriber.rb +25 -0
  101. data/lib/action_dispatch/middleware/actionable_exceptions.rb +7 -6
  102. data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
  103. data/lib/action_dispatch/middleware/callbacks.rb +4 -0
  104. data/lib/action_dispatch/middleware/cookies.rb +192 -194
  105. data/lib/action_dispatch/middleware/debug_exceptions.rb +36 -27
  106. data/lib/action_dispatch/middleware/debug_locks.rb +18 -13
  107. data/lib/action_dispatch/middleware/debug_view.rb +9 -2
  108. data/lib/action_dispatch/middleware/exception_wrapper.rb +181 -27
  109. data/lib/action_dispatch/middleware/executor.rb +9 -1
  110. data/lib/action_dispatch/middleware/flash.rb +65 -46
  111. data/lib/action_dispatch/middleware/host_authorization.rb +22 -17
  112. data/lib/action_dispatch/middleware/public_exceptions.rb +12 -8
  113. data/lib/action_dispatch/middleware/reloader.rb +9 -5
  114. data/lib/action_dispatch/middleware/remote_ip.rb +88 -83
  115. data/lib/action_dispatch/middleware/request_id.rb +15 -8
  116. data/lib/action_dispatch/middleware/server_timing.rb +8 -6
  117. data/lib/action_dispatch/middleware/session/abstract_store.rb +7 -0
  118. data/lib/action_dispatch/middleware/session/cache_store.rb +14 -7
  119. data/lib/action_dispatch/middleware/session/cookie_store.rb +32 -25
  120. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +9 -3
  121. data/lib/action_dispatch/middleware/show_exceptions.rb +42 -28
  122. data/lib/action_dispatch/middleware/ssl.rb +60 -45
  123. data/lib/action_dispatch/middleware/stack.rb +15 -9
  124. data/lib/action_dispatch/middleware/static.rb +40 -34
  125. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
  126. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +4 -4
  127. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
  128. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +7 -7
  129. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
  130. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +17 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +16 -12
  132. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -1
  133. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
  134. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +4 -4
  135. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  136. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  137. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +3 -0
  138. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +47 -38
  139. data/lib/action_dispatch/railtie.rb +12 -4
  140. data/lib/action_dispatch/request/session.rb +39 -27
  141. data/lib/action_dispatch/request/utils.rb +10 -3
  142. data/lib/action_dispatch/routing/endpoint.rb +2 -0
  143. data/lib/action_dispatch/routing/inspector.rb +59 -9
  144. data/lib/action_dispatch/routing/mapper.rb +686 -639
  145. data/lib/action_dispatch/routing/polymorphic_routes.rb +70 -61
  146. data/lib/action_dispatch/routing/redirection.rb +52 -38
  147. data/lib/action_dispatch/routing/route_set.rb +106 -62
  148. data/lib/action_dispatch/routing/routes_proxy.rb +16 -19
  149. data/lib/action_dispatch/routing/url_for.rb +131 -122
  150. data/lib/action_dispatch/routing.rb +152 -150
  151. data/lib/action_dispatch/system_test_case.rb +91 -81
  152. data/lib/action_dispatch/system_testing/browser.rb +27 -19
  153. data/lib/action_dispatch/system_testing/driver.rb +16 -22
  154. data/lib/action_dispatch/system_testing/server.rb +2 -0
  155. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +53 -31
  156. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +2 -0
  157. data/lib/action_dispatch/testing/assertion_response.rb +9 -7
  158. data/lib/action_dispatch/testing/assertions/response.rb +36 -26
  159. data/lib/action_dispatch/testing/assertions/routing.rb +203 -95
  160. data/lib/action_dispatch/testing/assertions.rb +5 -1
  161. data/lib/action_dispatch/testing/integration.rb +240 -229
  162. data/lib/action_dispatch/testing/request_encoder.rb +6 -1
  163. data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
  164. data/lib/action_dispatch/testing/test_process.rb +14 -9
  165. data/lib/action_dispatch/testing/test_request.rb +4 -2
  166. data/lib/action_dispatch/testing/test_response.rb +34 -19
  167. data/lib/action_dispatch.rb +52 -21
  168. data/lib/action_pack/gem_version.rb +6 -4
  169. data/lib/action_pack/version.rb +3 -1
  170. data/lib/action_pack.rb +18 -17
  171. metadata +86 -27
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "active_support/notifications"
4
6
 
5
7
  module ActionDispatch
6
8
  class ServerTiming
7
- SERVER_TIMING_HEADER = "Server-Timing"
8
-
9
9
  class Subscriber # :nodoc:
10
10
  include Singleton
11
11
  KEY = :action_dispatch_server_timing_events
@@ -31,8 +31,8 @@ module ActionDispatch
31
31
 
32
32
  def ensure_subscribed
33
33
  @mutex.synchronize do
34
- # Subscribe to all events, except those beginning with "!"
35
- # Ideally we would be more selective of what is being measured
34
+ # Subscribe to all events, except those beginning with "!" Ideally we would be
35
+ # more selective of what is being measured
36
36
  @subscriber ||= ActiveSupport::Notifications.subscribe(/\A[^!]/, self)
37
37
  end
38
38
  end
@@ -67,8 +67,10 @@ module ActionDispatch
67
67
  "%s;dur=%.2f" % [event_name, events_collection.sum(&:duration)]
68
68
  end
69
69
 
70
- header_info.prepend(headers[SERVER_TIMING_HEADER]) if headers[SERVER_TIMING_HEADER].present?
71
- headers[SERVER_TIMING_HEADER] = header_info.join(", ")
70
+ if headers[ActionDispatch::Constants::SERVER_TIMING].present?
71
+ header_info.prepend(headers[ActionDispatch::Constants::SERVER_TIMING])
72
+ end
73
+ headers[ActionDispatch::Constants::SERVER_TIMING] = header_info.join(", ")
72
74
 
73
75
  response
74
76
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "rack/utils"
4
6
  require "rack/request"
5
7
  require "rack/session/abstract/id"
@@ -67,6 +69,11 @@ module ActionDispatch
67
69
  end
68
70
 
69
71
  module SessionObject # :nodoc:
72
+ def commit_session(req, res)
73
+ req.commit_csrf_token
74
+ super(req, res)
75
+ end
76
+
70
77
  def prepare_session(req)
71
78
  Request::Session.create(self, req, @default_options)
72
79
  end
@@ -1,17 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "action_dispatch/middleware/session/abstract_store"
4
6
 
5
7
  module ActionDispatch
6
8
  module Session
7
- # A session store that uses an ActiveSupport::Cache::Store to store the sessions. This store is most useful
8
- # if you don't store critical data in your sessions and you don't need them to live for extended periods
9
- # of time.
9
+ # # Action Dispatch Session CacheStore
10
+ #
11
+ # A session store that uses an ActiveSupport::Cache::Store to store the
12
+ # sessions. This store is most useful if you don't store critical data in your
13
+ # sessions and you don't need them to live for extended periods of time.
14
+ #
15
+ # #### Options
16
+ # * `cache` - The cache to use. If it is not specified, `Rails.cache`
17
+ # will be used.
18
+ # * `expire_after` - The length of time a session will be stored before
19
+ # automatically expiring. By default, the `:expires_in` option of the cache
20
+ # is used.
10
21
  #
11
- # ==== Options
12
- # * <tt>cache</tt> - The cache to use. If it is not specified, <tt>Rails.cache</tt> will be used.
13
- # * <tt>expire_after</tt> - The length of time a session will be stored before automatically expiring.
14
- # By default, the <tt>:expires_in</tt> option of the cache is used.
15
22
  class CacheStore < AbstractSecureStore
16
23
  def initialize(app, options = {})
17
24
  @cache = options[:cache] || Rails.cache
@@ -1,51 +1,54 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "active_support/core_ext/hash/keys"
4
6
  require "action_dispatch/middleware/session/abstract_store"
5
7
  require "rack/session/cookie"
6
8
 
7
9
  module ActionDispatch
8
10
  module Session
9
- # This cookie-based session store is the Rails default. It is
10
- # dramatically faster than the alternatives.
11
+ # # Action Dispatch Session CookieStore
12
+ #
13
+ # This cookie-based session store is the Rails default. It is dramatically
14
+ # faster than the alternatives.
11
15
  #
12
16
  # Sessions typically contain at most a user ID and flash message; both fit
13
- # within the 4096 bytes cookie size limit. A +CookieOverflow+ exception is raised if
14
- # you attempt to store more than 4096 bytes of data.
17
+ # within the 4096 bytes cookie size limit. A `CookieOverflow` exception is
18
+ # raised if you attempt to store more than 4096 bytes of data.
15
19
  #
16
- # The cookie jar used for storage is automatically configured to be the
17
- # best possible option given your application's configuration.
20
+ # The cookie jar used for storage is automatically configured to be the best
21
+ # possible option given your application's configuration.
18
22
  #
19
- # Your cookies will be encrypted using your application's +secret_key_base+. This
20
- # goes a step further than signed cookies in that encrypted cookies cannot
23
+ # Your cookies will be encrypted using your application's `secret_key_base`.
24
+ # This goes a step further than signed cookies in that encrypted cookies cannot
21
25
  # be altered or read by users. This is the default starting in Rails 4.
22
26
  #
23
27
  # Configure your session store in an initializer:
24
28
  #
25
- # Rails.application.config.session_store :cookie_store, key: '_your_app_session'
29
+ # Rails.application.config.session_store :cookie_store, key: '_your_app_session'
26
30
  #
27
- # 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>.
29
- # In all other environments, it is stored encrypted in the
30
- # <tt>config/credentials.yml.enc</tt> file.
31
+ # In the development and test environments your application's `secret_key_base`
32
+ # is generated by Rails and stored in a temporary file in
33
+ # `tmp/local_secret.txt`. In all other environments, it is stored encrypted in
34
+ # the `config/credentials.yml.enc` file.
31
35
  #
32
- # If your application was not updated to Rails 5.2 defaults, the +secret_key_base+
33
- # will be found in the old <tt>config/secrets.yml</tt> file.
36
+ # If your application was not updated to Rails 5.2 defaults, the
37
+ # `secret_key_base` will be found in the old `config/secrets.yml` file.
34
38
  #
35
- # Note that changing your +secret_key_base+ will invalidate all existing session.
36
- # Additionally, you should take care to make sure you are not relying on the
37
- # ability to decode signed cookies generated by your app in external
39
+ # Note that changing your `secret_key_base` will invalidate all existing
40
+ # session. Additionally, you should take care to make sure you are not relying
41
+ # on the ability to decode signed cookies generated by your app in external
38
42
  # applications or JavaScript before changing it.
39
43
  #
40
- # Because CookieStore extends +Rack::Session::Abstract::Persisted+, many of the
41
- # options described there can be used to customize the session cookie that
42
- # is generated. For example:
44
+ # Because CookieStore extends `Rack::Session::Abstract::Persisted`, many of the
45
+ # options described there can be used to customize the session cookie that is
46
+ # generated. For example:
43
47
  #
44
- # Rails.application.config.session_store :cookie_store, expire_after: 14.days
48
+ # Rails.application.config.session_store :cookie_store, expire_after: 14.days
45
49
  #
46
50
  # would set the session cookie to expire automatically 14 days after creation.
47
- # Other useful options include <tt>:key</tt>, <tt>:secure</tt>,
48
- # <tt>:httponly</tt>, and <tt>:same_site</tt>.
51
+ # Other useful options include `:key`, `:secure`, `:httponly`, and `:same_site`.
49
52
  class CookieStore < AbstractSecureStore
50
53
  class SessionId < DelegateClass(Rack::Session::SessionId)
51
54
  attr_reader :cookie_value
@@ -56,8 +59,12 @@ module ActionDispatch
56
59
  end
57
60
  end
58
61
 
62
+ DEFAULT_SAME_SITE = proc { |request| request.cookies_same_site_protection } # :nodoc:
63
+
59
64
  def initialize(app, options = {})
60
- super(app, options.merge!(cookie_only: true))
65
+ options[:cookie_only] = true
66
+ options[:same_site] = DEFAULT_SAME_SITE if !options.key?(:same_site)
67
+ super
61
68
  end
62
69
 
63
70
  def delete_session(req, session_id, options)
@@ -1,19 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "action_dispatch/middleware/session/abstract_store"
4
6
  begin
5
7
  require "rack/session/dalli"
6
8
  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"
9
+ warn "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install"
8
10
  raise e
9
11
  end
10
12
 
11
13
  module ActionDispatch
12
14
  module Session
15
+ # # Action Dispatch Session MemCacheStore
16
+ #
13
17
  # A session store that uses MemCache to implement storage.
14
18
  #
15
- # ==== Options
16
- # * <tt>expire_after</tt> - The length of time a session will be stored before automatically expiring.
19
+ # #### Options
20
+ # * `expire_after` - The length of time a session will be stored before
21
+ # automatically expiring.
22
+ #
17
23
  class MemCacheStore < Rack::Session::Dalli
18
24
  include Compatibility
19
25
  include StaleSessionCheck
@@ -1,23 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "action_dispatch/middleware/exception_wrapper"
4
6
 
5
7
  module ActionDispatch
6
- # This middleware rescues any exception returned by the application
7
- # and calls an exceptions app that will wrap it in a format for the end user.
8
+ # # Action Dispatch ShowExceptions
9
+ #
10
+ # This middleware rescues any exception returned by the application and calls an
11
+ # exceptions app that will wrap it in a format for the end user.
8
12
  #
9
13
  # The exceptions app should be passed as a parameter on initialization of
10
- # +ShowExceptions+. Every time there is an exception, +ShowExceptions+ will
11
- # store the exception in <tt>env["action_dispatch.exception"]</tt>, rewrite
12
- # the +PATH_INFO+ to the exception status code and call the Rack app.
14
+ # `ShowExceptions`. Every time there is an exception, `ShowExceptions` will
15
+ # store the exception in `env["action_dispatch.exception"]`, rewrite the
16
+ # `PATH_INFO` to the exception status code, and call the Rack app.
13
17
  #
14
- # In \Rails applications, the exceptions app can be configured with
15
- # +config.exceptions_app+, which defaults to ActionDispatch::PublicExceptions.
18
+ # In Rails applications, the exceptions app can be configured with
19
+ # `config.exceptions_app`, which defaults to ActionDispatch::PublicExceptions.
16
20
  #
17
- # If the application returns an <tt>"X-Cascade" => "pass"</tt> response, this
18
- # middleware will send an empty response as a result with the correct status
19
- # code. If any exception happens inside the exceptions app, this middleware
20
- # catches the exceptions and returns a failsafe response.
21
+ # If the application returns a response with the `X-Cascade` header set to
22
+ # `"pass"`, this middleware will send an empty response as a result with the
23
+ # correct status code. If any exception happens inside the exceptions app, this
24
+ # middleware catches the exceptions and returns a failsafe response.
21
25
  class ShowExceptions
22
26
  def initialize(app, exceptions_app)
23
27
  @app = app
@@ -25,33 +29,35 @@ module ActionDispatch
25
29
  end
26
30
 
27
31
  def call(env)
28
- request = ActionDispatch::Request.new env
29
32
  @app.call(env)
30
33
  rescue Exception => exception
31
- if request.show_exceptions?
32
- render_exception(request, exception)
34
+ request = ActionDispatch::Request.new env
35
+ backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
36
+ wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
37
+ request.set_header "action_dispatch.exception", wrapper.unwrapped_exception
38
+ request.set_header "action_dispatch.report_exception", !wrapper.rescue_response?
39
+
40
+ if wrapper.show?(request)
41
+ render_exception(request.dup, wrapper)
33
42
  else
34
43
  raise exception
35
44
  end
36
45
  end
37
46
 
38
47
  private
39
- def render_exception(request, exception)
40
- backtrace_cleaner = request.get_header "action_dispatch.backtrace_cleaner"
41
- wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
42
- status = wrapper.status_code
43
- request.set_header "action_dispatch.exception", wrapper.unwrapped_exception
48
+ def render_exception(request, wrapper)
49
+ status = wrapper.status_code
44
50
  request.set_header "action_dispatch.original_path", request.path_info
45
51
  request.set_header "action_dispatch.original_request_method", request.raw_request_method
46
52
  fallback_to_html_format_if_invalid_mime_type(request)
47
53
  request.path_info = "/#{status}"
48
54
  request.request_method = "GET"
49
55
  response = @exceptions_app.call(request.env)
50
- response[1]["X-Cascade"] == "pass" ? pass_response(status) : response
56
+ response[1][Constants::X_CASCADE] == "pass" ? pass_response(status) : response
51
57
  rescue Exception => failsafe_error
52
58
  $stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
53
59
 
54
- [500, { "Content-Type" => "text/plain" },
60
+ [500, { Rack::CONTENT_TYPE => "text/plain; charset=utf-8" },
55
61
  ["500 Internal Server Error\n" \
56
62
  "If you are the administrator of this website, then please read this web " \
57
63
  "application's log file and/or the web server's log file to find out what " \
@@ -59,16 +65,24 @@ module ActionDispatch
59
65
  end
60
66
 
61
67
  def fallback_to_html_format_if_invalid_mime_type(request)
62
- # If the MIME type for the request is invalid then the
63
- # @exceptions_app may not be able to handle it. To make it
64
- # easier to handle, we switch to HTML.
65
- request.formats
66
- rescue ActionDispatch::Http::MimeNegotiation::InvalidType
67
- request.set_header "HTTP_ACCEPT", "text/html"
68
+ # If the MIME type for the request is invalid then the @exceptions_app may not
69
+ # be able to handle it. To make it easier to handle, we switch to 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
68
81
  end
69
82
 
70
83
  def pass_response(status)
71
- [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" }, []]
72
86
  end
73
87
  end
74
88
  end
@@ -1,55 +1,60 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionDispatch
4
- # This middleware is added to the stack when <tt>config.force_ssl = true</tt>, and is passed
5
- # the options set in +config.ssl_options+. It does three jobs to enforce secure HTTP
6
- # requests:
6
+ # # Action Dispatch SSL
7
+ #
8
+ # This middleware is added to the stack when `config.force_ssl = true`, and is
9
+ # passed the options set in `config.ssl_options`. It does three jobs to enforce
10
+ # secure HTTP requests:
11
+ #
12
+ # 1. **TLS redirect**: Permanently redirects `http://` requests to `https://`
13
+ # with the same URL host, path, etc. Enabled by default. Set
14
+ # `config.ssl_options` to modify the destination URL (e.g. `redirect: {
15
+ # host: "secure.widgets.com", port: 8080 }`), or set `redirect: false` to
16
+ # disable this feature.
17
+ #
18
+ # Requests can opt-out of redirection with `exclude`:
7
19
  #
8
- # 1. <b>TLS redirect</b>: Permanently redirects +http://+ requests to +https://+
9
- # with the same URL host, path, etc. Enabled by default. Set +config.ssl_options+
10
- # to modify the destination URL
11
- # (e.g. <tt>redirect: { host: "secure.widgets.com", port: 8080 }</tt>), or set
12
- # <tt>redirect: false</tt> to disable this feature.
20
+ # config.ssl_options = { redirect: { exclude: -> request { request.path == "/up" } } }
13
21
  #
14
- # Requests can opt-out of redirection with +exclude+:
22
+ # Cookies will not be flagged as secure for excluded requests.
15
23
  #
16
- # config.ssl_options = { redirect: { exclude: -> request { /healthcheck/.match?(request.path) } } }
24
+ # 2. **Secure cookies**: Sets the `secure` flag on cookies to tell browsers
25
+ # they must not be sent along with `http://` requests. Enabled by default.
26
+ # Set `config.ssl_options` with `secure_cookies: false` to disable this
27
+ # feature.
17
28
  #
18
- # Cookies will not be flagged as secure for excluded requests.
29
+ # 3. **HTTP Strict Transport Security (HSTS)**: Tells the browser to remember
30
+ # this site as TLS-only and automatically redirect non-TLS requests. Enabled
31
+ # by default. Configure `config.ssl_options` with `hsts: false` to disable.
19
32
  #
20
- # 2. <b>Secure cookies</b>: Sets the +secure+ flag on cookies to tell browsers they
21
- # must not be sent along with +http://+ requests. Enabled by default. Set
22
- # +config.ssl_options+ with <tt>secure_cookies: false</tt> to disable this feature.
33
+ # Set `config.ssl_options` with `hsts: { ... }` to configure HSTS:
23
34
  #
24
- # 3. <b>HTTP Strict Transport Security (HSTS)</b>: Tells the browser to remember
25
- # this site as TLS-only and automatically redirect non-TLS requests.
26
- # Enabled by default. Configure +config.ssl_options+ with <tt>hsts: false</tt> to disable.
35
+ # * `expires`: How long, in seconds, these settings will stick. The
36
+ # minimum required to qualify for browser preload lists is 1 year.
37
+ # Defaults to 2 years (recommended).
27
38
  #
28
- # Set +config.ssl_options+ with <tt>hsts: { ... }</tt> to configure HSTS:
39
+ # * `subdomains`: Set to `true` to tell the browser to apply these
40
+ # settings to all subdomains. This protects your cookies from
41
+ # interception by a vulnerable site on a subdomain. Defaults to `true`.
29
42
  #
30
- # * +expires+: How long, in seconds, these settings will stick. The minimum
31
- # required to qualify for browser preload lists is 1 year. Defaults to
32
- # 2 years (recommended).
43
+ # * `preload`: Advertise that this site may be included in browsers'
44
+ # preloaded HSTS lists. HSTS protects your site on every visit *except
45
+ # the first visit* since it hasn't seen your HSTS header yet. To close
46
+ # this gap, browser vendors include a baked-in list of HSTS-enabled
47
+ # sites. Go to https://hstspreload.org to submit your site for
48
+ # inclusion. Defaults to `false`.
33
49
  #
34
- # * +subdomains+: Set to +true+ to tell the browser to apply these settings
35
- # to all subdomains. This protects your cookies from interception by a
36
- # vulnerable site on a subdomain. Defaults to +true+.
37
50
  #
38
- # * +preload+: Advertise that this site may be included in browsers'
39
- # preloaded HSTS lists. HSTS protects your site on every visit <i>except the
40
- # first visit</i> since it hasn't seen your HSTS header yet. To close this
41
- # gap, browser vendors include a baked-in list of HSTS-enabled sites.
42
- # Go to https://hstspreload.org to submit your site for inclusion.
43
- # Defaults to +false+.
51
+ # To turn off HSTS, omitting the header is not enough. Browsers will
52
+ # remember the original HSTS directive until it expires. Instead, use the
53
+ # header to tell browsers to expire HSTS immediately. Setting `hsts: false`
54
+ # is a shortcut for `hsts: { expires: 0 }`.
44
55
  #
45
- # To turn off HSTS, omitting the header is not enough. Browsers will remember the
46
- # original HSTS directive until it expires. Instead, use the header to tell browsers to
47
- # expire HSTS immediately. Setting <tt>hsts: false</tt> is a shortcut for
48
- # <tt>hsts: { expires: 0 }</tt>.
49
56
  class SSL
50
- # :stopdoc:
51
-
52
- # Default to 2 years as recommended on hstspreload.org.
57
+ # :stopdoc: Default to 2 years as recommended on hstspreload.org.
53
58
  HSTS_EXPIRES_IN = 63072000
54
59
 
55
60
  PERMANENT_REDIRECT_REQUEST_METHODS = %w[GET HEAD] # :nodoc:
@@ -86,13 +91,13 @@ module ActionDispatch
86
91
 
87
92
  private
88
93
  def set_hsts_header!(headers)
89
- headers["Strict-Transport-Security"] ||= @hsts_header
94
+ headers[Constants::STRICT_TRANSPORT_SECURITY] ||= @hsts_header
90
95
  end
91
96
 
92
97
  def normalize_hsts_options(options)
93
98
  case options
94
- # Explicitly disabling HSTS clears the existing setting from browsers
95
- # by setting expiry to 0.
99
+ # Explicitly disabling HSTS clears the existing setting from browsers by setting
100
+ # expiry to 0.
96
101
  when false
97
102
  self.class.default_hsts_options.merge(expires: 0)
98
103
  # Default to enabled, with default options.
@@ -112,23 +117,33 @@ module ActionDispatch
112
117
  end
113
118
 
114
119
  def flag_cookies_as_secure!(headers)
115
- if cookies = headers["Set-Cookie"]
116
- cookies = cookies.split("\n")
120
+ cookies = headers[Rack::SET_COOKIE]
121
+ return unless cookies
117
122
 
118
- headers["Set-Cookie"] = cookies.map { |cookie|
123
+ if Gem::Version.new(Rack::RELEASE) < Gem::Version.new("3")
124
+ cookies = cookies.split("\n")
125
+ headers[Rack::SET_COOKIE] = cookies.map { |cookie|
119
126
  if !/;\s*secure\s*(;|$)/i.match?(cookie)
120
127
  "#{cookie}; secure"
121
128
  else
122
129
  cookie
123
130
  end
124
131
  }.join("\n")
132
+ else
133
+ headers[Rack::SET_COOKIE] = Array(cookies).map do |cookie|
134
+ if !/;\s*secure\s*(;|$)/i.match?(cookie)
135
+ "#{cookie}; secure"
136
+ else
137
+ cookie
138
+ end
139
+ end
125
140
  end
126
141
  end
127
142
 
128
143
  def redirect_to_https(request)
129
144
  [ @redirect.fetch(:status, redirection_status(request)),
130
- { "Content-Type" => "text/html",
131
- "Location" => https_location_for(request) },
145
+ { Rack::CONTENT_TYPE => "text/html; charset=utf-8",
146
+ Constants::LOCATION => https_location_for(request) },
132
147
  (@redirect[:body] || []) ]
133
148
  end
134
149
 
@@ -1,9 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "active_support/inflector/methods"
4
6
  require "active_support/dependencies"
5
7
 
6
8
  module ActionDispatch
9
+ # # Action Dispatch MiddlewareStack
10
+ #
11
+ # Read more about [Rails middleware
12
+ # stack](https://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack)
13
+ # in the guides.
7
14
  class MiddlewareStack
8
15
  class Middleware
9
16
  attr_reader :args, :block, :klass
@@ -20,13 +27,13 @@ module ActionDispatch
20
27
  case middleware
21
28
  when Middleware
22
29
  klass == middleware.klass
23
- when Class
30
+ when Module
24
31
  klass == middleware
25
32
  end
26
33
  end
27
34
 
28
35
  def inspect
29
- if klass.is_a?(Class)
36
+ if klass.is_a?(Module)
30
37
  klass.to_s
31
38
  else
32
39
  klass.class.to_s
@@ -42,9 +49,8 @@ module ActionDispatch
42
49
  end
43
50
  end
44
51
 
45
- # This class is used to instrument the execution of a single middleware.
46
- # It proxies the +call+ method transparently and instruments the method
47
- # call.
52
+ # This class is used to instrument the execution of a single middleware. It
53
+ # proxies the `call` method transparently and instruments the method call.
48
54
  class InstrumentationProxy
49
55
  EVENT_NAME = "process_middleware.action_dispatch"
50
56
 
@@ -120,16 +126,16 @@ module ActionDispatch
120
126
 
121
127
  # Deletes a middleware from the middleware stack.
122
128
  #
123
- # Returns the array of middlewares not including the deleted item, or
124
- # returns nil if the target is not found.
129
+ # Returns the array of middlewares not including the deleted item, or returns
130
+ # nil if the target is not found.
125
131
  def delete(target)
126
132
  middlewares.reject! { |m| m.name == target.name }
127
133
  end
128
134
 
129
135
  # Deletes a middleware from the middleware stack.
130
136
  #
131
- # Returns the array of middlewares not including the deleted item, or
132
- # raises +RuntimeError+ if the target is not found.
137
+ # Returns the array of middlewares not including the deleted item, or raises
138
+ # `RuntimeError` if the target is not found.
133
139
  def delete!(target)
134
140
  delete(target) || (raise "No such middleware to remove: #{target.inspect}")
135
141
  end