actionpack 4.2.11.1 → 6.1.3.2

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

Potentially problematic release.


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

Files changed (187) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +291 -489
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +9 -9
  5. data/lib/abstract_controller/asset_paths.rb +2 -0
  6. data/lib/abstract_controller/base.rb +81 -51
  7. data/lib/{action_controller → abstract_controller}/caching/fragments.rb +64 -17
  8. data/lib/abstract_controller/caching.rb +66 -0
  9. data/lib/abstract_controller/callbacks.rb +61 -33
  10. data/lib/abstract_controller/collector.rb +9 -13
  11. data/lib/abstract_controller/error.rb +6 -0
  12. data/lib/abstract_controller/helpers.rb +115 -99
  13. data/lib/abstract_controller/logger.rb +2 -0
  14. data/lib/abstract_controller/railties/routes_helpers.rb +21 -3
  15. data/lib/abstract_controller/rendering.rb +48 -47
  16. data/lib/abstract_controller/translation.rb +17 -8
  17. data/lib/abstract_controller/url_for.rb +2 -0
  18. data/lib/abstract_controller.rb +13 -5
  19. data/lib/action_controller/api/api_rendering.rb +16 -0
  20. data/lib/action_controller/api.rb +150 -0
  21. data/lib/action_controller/base.rb +29 -24
  22. data/lib/action_controller/caching.rb +12 -57
  23. data/lib/action_controller/form_builder.rb +50 -0
  24. data/lib/action_controller/log_subscriber.rb +17 -19
  25. data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
  26. data/lib/action_controller/metal/conditional_get.rb +134 -46
  27. data/lib/action_controller/metal/content_security_policy.rb +51 -0
  28. data/lib/action_controller/metal/cookies.rb +6 -4
  29. data/lib/action_controller/metal/data_streaming.rb +30 -50
  30. data/lib/action_controller/metal/default_headers.rb +17 -0
  31. data/lib/action_controller/metal/etag_with_flash.rb +18 -0
  32. data/lib/action_controller/metal/etag_with_template_digest.rb +21 -16
  33. data/lib/action_controller/metal/exceptions.rb +63 -15
  34. data/lib/action_controller/metal/flash.rb +9 -8
  35. data/lib/action_controller/metal/head.rb +26 -21
  36. data/lib/action_controller/metal/helpers.rb +37 -18
  37. data/lib/action_controller/metal/http_authentication.rb +81 -73
  38. data/lib/action_controller/metal/implicit_render.rb +53 -9
  39. data/lib/action_controller/metal/instrumentation.rb +32 -35
  40. data/lib/action_controller/metal/live.rb +102 -120
  41. data/lib/action_controller/metal/logging.rb +20 -0
  42. data/lib/action_controller/metal/mime_responds.rb +49 -47
  43. data/lib/action_controller/metal/parameter_encoding.rb +82 -0
  44. data/lib/action_controller/metal/params_wrapper.rb +83 -66
  45. data/lib/action_controller/metal/permissions_policy.rb +46 -0
  46. data/lib/action_controller/metal/redirecting.rb +53 -32
  47. data/lib/action_controller/metal/renderers.rb +87 -44
  48. data/lib/action_controller/metal/rendering.rb +77 -50
  49. data/lib/action_controller/metal/request_forgery_protection.rb +267 -103
  50. data/lib/action_controller/metal/rescue.rb +10 -17
  51. data/lib/action_controller/metal/streaming.rb +12 -11
  52. data/lib/action_controller/metal/strong_parameters.rb +714 -186
  53. data/lib/action_controller/metal/testing.rb +2 -17
  54. data/lib/action_controller/metal/url_for.rb +19 -10
  55. data/lib/action_controller/metal.rb +104 -87
  56. data/lib/action_controller/railtie.rb +28 -10
  57. data/lib/action_controller/railties/helpers.rb +3 -1
  58. data/lib/action_controller/renderer.rb +141 -0
  59. data/lib/action_controller/template_assertions.rb +11 -0
  60. data/lib/action_controller/test_case.rb +296 -422
  61. data/lib/action_controller.rb +34 -23
  62. data/lib/action_dispatch/http/cache.rb +107 -56
  63. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  64. data/lib/action_dispatch/http/content_security_policy.rb +286 -0
  65. data/lib/action_dispatch/http/filter_parameters.rb +32 -25
  66. data/lib/action_dispatch/http/filter_redirect.rb +10 -12
  67. data/lib/action_dispatch/http/headers.rb +55 -22
  68. data/lib/action_dispatch/http/mime_negotiation.rb +79 -51
  69. data/lib/action_dispatch/http/mime_type.rb +153 -121
  70. data/lib/action_dispatch/http/mime_types.rb +20 -6
  71. data/lib/action_dispatch/http/parameters.rb +90 -40
  72. data/lib/action_dispatch/http/permissions_policy.rb +173 -0
  73. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  74. data/lib/action_dispatch/http/request.rb +226 -121
  75. data/lib/action_dispatch/http/response.rb +248 -113
  76. data/lib/action_dispatch/http/upload.rb +21 -7
  77. data/lib/action_dispatch/http/url.rb +182 -100
  78. data/lib/action_dispatch/journey/formatter.rb +90 -43
  79. data/lib/action_dispatch/journey/gtg/builder.rb +28 -41
  80. data/lib/action_dispatch/journey/gtg/simulator.rb +11 -16
  81. data/lib/action_dispatch/journey/gtg/transition_table.rb +23 -21
  82. data/lib/action_dispatch/journey/nfa/dot.rb +3 -14
  83. data/lib/action_dispatch/journey/nodes/node.rb +29 -15
  84. data/lib/action_dispatch/journey/parser.rb +17 -16
  85. data/lib/action_dispatch/journey/parser.y +4 -3
  86. data/lib/action_dispatch/journey/parser_extras.rb +12 -4
  87. data/lib/action_dispatch/journey/path/pattern.rb +58 -54
  88. data/lib/action_dispatch/journey/route.rb +100 -32
  89. data/lib/action_dispatch/journey/router/utils.rb +29 -18
  90. data/lib/action_dispatch/journey/router.rb +55 -51
  91. data/lib/action_dispatch/journey/routes.rb +17 -17
  92. data/lib/action_dispatch/journey/scanner.rb +26 -17
  93. data/lib/action_dispatch/journey/visitors.rb +98 -54
  94. data/lib/action_dispatch/journey.rb +5 -5
  95. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  96. data/lib/action_dispatch/middleware/callbacks.rb +3 -6
  97. data/lib/action_dispatch/middleware/cookies.rb +347 -217
  98. data/lib/action_dispatch/middleware/debug_exceptions.rb +135 -63
  99. data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
  100. data/lib/action_dispatch/middleware/debug_view.rb +66 -0
  101. data/lib/action_dispatch/middleware/exception_wrapper.rb +115 -71
  102. data/lib/action_dispatch/middleware/executor.rb +21 -0
  103. data/lib/action_dispatch/middleware/flash.rb +78 -54
  104. data/lib/action_dispatch/middleware/host_authorization.rb +130 -0
  105. data/lib/action_dispatch/middleware/public_exceptions.rb +32 -27
  106. data/lib/action_dispatch/middleware/reloader.rb +5 -91
  107. data/lib/action_dispatch/middleware/remote_ip.rb +53 -45
  108. data/lib/action_dispatch/middleware/request_id.rb +17 -10
  109. data/lib/action_dispatch/middleware/session/abstract_store.rb +41 -26
  110. data/lib/action_dispatch/middleware/session/cache_store.rb +24 -14
  111. data/lib/action_dispatch/middleware/session/cookie_store.rb +74 -75
  112. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -2
  113. data/lib/action_dispatch/middleware/show_exceptions.rb +28 -23
  114. data/lib/action_dispatch/middleware/ssl.rb +118 -35
  115. data/lib/action_dispatch/middleware/stack.rb +82 -41
  116. data/lib/action_dispatch/middleware/static.rb +156 -89
  117. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  119. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +4 -14
  121. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  122. data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +4 -2
  123. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  124. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +45 -35
  125. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -0
  126. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +23 -4
  128. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
  129. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +15 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +105 -8
  132. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +2 -2
  135. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -1
  136. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +3 -3
  137. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  138. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  139. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
  140. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +87 -64
  141. data/lib/action_dispatch/railtie.rb +27 -13
  142. data/lib/action_dispatch/request/session.rb +109 -61
  143. data/lib/action_dispatch/request/utils.rb +90 -23
  144. data/lib/action_dispatch/routing/endpoint.rb +9 -2
  145. data/lib/action_dispatch/routing/inspector.rb +141 -102
  146. data/lib/action_dispatch/routing/mapper.rb +811 -473
  147. data/lib/action_dispatch/routing/polymorphic_routes.rb +167 -143
  148. data/lib/action_dispatch/routing/redirection.rb +37 -27
  149. data/lib/action_dispatch/routing/route_set.rb +363 -331
  150. data/lib/action_dispatch/routing/routes_proxy.rb +32 -5
  151. data/lib/action_dispatch/routing/url_for.rb +66 -26
  152. data/lib/action_dispatch/routing.rb +36 -36
  153. data/lib/action_dispatch/system_test_case.rb +190 -0
  154. data/lib/action_dispatch/system_testing/browser.rb +86 -0
  155. data/lib/action_dispatch/system_testing/driver.rb +67 -0
  156. data/lib/action_dispatch/system_testing/server.rb +31 -0
  157. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +138 -0
  158. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +29 -0
  159. data/lib/action_dispatch/testing/assertion_response.rb +46 -0
  160. data/lib/action_dispatch/testing/assertions/response.rb +44 -22
  161. data/lib/action_dispatch/testing/assertions/routing.rb +47 -31
  162. data/lib/action_dispatch/testing/assertions.rb +6 -4
  163. data/lib/action_dispatch/testing/integration.rb +391 -220
  164. data/lib/action_dispatch/testing/request_encoder.rb +55 -0
  165. data/lib/action_dispatch/testing/test_process.rb +53 -22
  166. data/lib/action_dispatch/testing/test_request.rb +27 -34
  167. data/lib/action_dispatch/testing/test_response.rb +11 -11
  168. data/lib/action_dispatch.rb +35 -21
  169. data/lib/action_pack/gem_version.rb +6 -4
  170. data/lib/action_pack/version.rb +3 -1
  171. data/lib/action_pack.rb +4 -2
  172. metadata +78 -48
  173. data/lib/action_controller/metal/force_ssl.rb +0 -97
  174. data/lib/action_controller/metal/hide_actions.rb +0 -40
  175. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  176. data/lib/action_controller/middleware.rb +0 -39
  177. data/lib/action_controller/model_naming.rb +0 -12
  178. data/lib/action_dispatch/http/parameter_filter.rb +0 -72
  179. data/lib/action_dispatch/journey/backwards.rb +0 -5
  180. data/lib/action_dispatch/journey/nfa/builder.rb +0 -76
  181. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
  182. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -163
  183. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  184. data/lib/action_dispatch/middleware/params_parser.rb +0 -60
  185. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  186. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  187. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -1,6 +1,8 @@
1
- require 'active_support/core_ext/hash/keys'
2
- require 'action_dispatch/middleware/session/abstract_store'
3
- require 'rack/session/cookie'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/keys"
4
+ require "action_dispatch/middleware/session/abstract_store"
5
+ require "rack/session/cookie"
4
6
 
5
7
  module ActionDispatch
6
8
  module Session
@@ -8,116 +10,113 @@ module ActionDispatch
8
10
  # dramatically faster than the alternatives.
9
11
  #
10
12
  # Sessions typically contain at most a user_id and flash message; both fit
11
- # within the 4K cookie size limit. A CookieOverflow exception is raised if
12
- # you attempt to store more than 4K of data.
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.
13
15
  #
14
16
  # The cookie jar used for storage is automatically configured to be the
15
17
  # best possible option given your application's configuration.
16
18
  #
17
- # If you only have secret_token set, your cookies will be signed, but
18
- # not encrypted. This means a user cannot alter their +user_id+ without
19
- # knowing your app's secret key, but can easily read their +user_id+. This
20
- # was the default for Rails 3 apps.
21
- #
22
- # If you have secret_key_base set, your cookies will be encrypted. This
19
+ # Your cookies will be encrypted using your apps secret_key_base. This
23
20
  # goes a step further than signed cookies in that encrypted cookies cannot
24
21
  # be altered or read by users. This is the default starting in Rails 4.
25
22
  #
26
- # If you have both secret_token and secret_key base set, your cookies will
27
- # be encrypted, and signed cookies generated by Rails 3 will be
28
- # transparently read and encrypted to provide a smooth upgrade path.
29
- #
30
- # Configure your session store in config/initializers/session_store.rb:
23
+ # Configure your session store in an initializer:
31
24
  #
32
25
  # Rails.application.config.session_store :cookie_store, key: '_your_app_session'
33
26
  #
34
- # Configure your secret key in config/secrets.yml:
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.
35
31
  #
36
- # development:
37
- # secret_key_base: 'secret key'
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.
38
34
  #
39
- # To generate a secret key for an existing application, run `rake secret`.
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
38
+ # applications or JavaScript before changing it.
40
39
  #
41
- # If you are upgrading an existing Rails 3 app, you should leave your
42
- # existing secret_token in place and simply add the new secret_key_base.
43
- # Note that you should wait to set secret_key_base until you have 100% of
44
- # your userbase on Rails 4 and are reasonably sure you will not need to
45
- # rollback to Rails 3. This is because cookies signed based on the new
46
- # secret_key_base in Rails 4 are not backwards compatible with Rails 3.
47
- # You are free to leave your existing secret_token in place, not set the
48
- # new secret_key_base, and ignore the deprecation warnings until you are
49
- # reasonably sure that your upgrade is otherwise complete. Additionally,
50
- # you should take care to make sure you are not relying on the ability to
51
- # decode signed cookies generated by your app in external applications or
52
- # JavaScript before upgrading.
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:
53
43
  #
54
- # Note that changing the secret key will invalidate all existing sessions!
55
- class CookieStore < Rack::Session::Abstract::ID
56
- include Compatibility
57
- include StaleSessionCheck
58
- include SessionObject
44
+ # Rails.application.config.session_store :cookie_store, expire_after: 14.days
45
+ #
46
+ # would set the session cookie to expire automatically 14 days after creation.
47
+ # Other useful options include <tt>:key</tt>, <tt>:secure</tt> and
48
+ # <tt>:httponly</tt>.
49
+ class CookieStore < AbstractSecureStore
50
+ class SessionId < DelegateClass(Rack::Session::SessionId)
51
+ attr_reader :cookie_value
52
+
53
+ def initialize(session_id, cookie_value = {})
54
+ super(session_id)
55
+ @cookie_value = cookie_value
56
+ end
57
+ end
59
58
 
60
- def initialize(app, options={})
61
- super(app, options.merge!(:cookie_only => true))
59
+ def initialize(app, options = {})
60
+ super(app, options.merge!(cookie_only: true))
62
61
  end
63
62
 
64
- def destroy_session(env, session_id, options)
63
+ def delete_session(req, session_id, options)
65
64
  new_sid = generate_sid unless options[:drop]
66
65
  # Reset hash and Assign the new session id
67
- env["action_dispatch.request.unsigned_session_cookie"] = new_sid ? { "session_id" => new_sid } : {}
66
+ req.set_header("action_dispatch.request.unsigned_session_cookie", new_sid ? { "session_id" => new_sid.public_id } : {})
68
67
  new_sid
69
68
  end
70
69
 
71
- def load_session(env)
70
+ def load_session(req)
72
71
  stale_session_check! do
73
- data = unpacked_cookie_data(env)
72
+ data = unpacked_cookie_data(req)
74
73
  data = persistent_session_id!(data)
75
- [data["session_id"], data]
74
+ [Rack::Session::SessionId.new(data["session_id"]), data]
76
75
  end
77
76
  end
78
77
 
79
78
  private
80
-
81
- def extract_session_id(env)
82
- stale_session_check! do
83
- unpacked_cookie_data(env)["session_id"]
79
+ def extract_session_id(req)
80
+ stale_session_check! do
81
+ sid = unpacked_cookie_data(req)["session_id"]
82
+ sid && Rack::Session::SessionId.new(sid)
83
+ end
84
84
  end
85
- end
86
85
 
87
- def unpacked_cookie_data(env)
88
- env["action_dispatch.request.unsigned_session_cookie"] ||= begin
89
- stale_session_check! do
90
- if data = get_cookie(env)
91
- data.stringify_keys!
86
+ def unpacked_cookie_data(req)
87
+ req.fetch_header("action_dispatch.request.unsigned_session_cookie") do |k|
88
+ v = stale_session_check! do
89
+ if data = get_cookie(req)
90
+ data.stringify_keys!
91
+ end
92
+ data || {}
92
93
  end
93
- data || {}
94
+ req.set_header k, v
94
95
  end
95
96
  end
96
- end
97
97
 
98
- def persistent_session_id!(data, sid=nil)
99
- data ||= {}
100
- data["session_id"] ||= sid || generate_sid
101
- data
102
- end
98
+ def persistent_session_id!(data, sid = nil)
99
+ data ||= {}
100
+ data["session_id"] ||= sid || generate_sid.public_id
101
+ data
102
+ end
103
103
 
104
- def set_session(env, sid, session_data, options)
105
- session_data["session_id"] = sid
106
- session_data
107
- end
104
+ def write_session(req, sid, session_data, options)
105
+ session_data["session_id"] = sid.public_id
106
+ SessionId.new(sid, session_data)
107
+ end
108
108
 
109
- def set_cookie(env, session_id, cookie)
110
- cookie_jar(env)[@key] = cookie
111
- end
109
+ def set_cookie(request, session_id, cookie)
110
+ cookie_jar(request)[@key] = cookie
111
+ end
112
112
 
113
- def get_cookie(env)
114
- cookie_jar(env)[@key]
115
- end
113
+ def get_cookie(req)
114
+ cookie_jar(req)[@key]
115
+ end
116
116
 
117
- def cookie_jar(env)
118
- request = ActionDispatch::Request.new(env)
119
- request.cookie_jar.signed_or_encrypted
120
- end
117
+ def cookie_jar(request)
118
+ request.cookie_jar.signed_or_encrypted
119
+ end
121
120
  end
122
121
  end
123
122
  end
@@ -1,6 +1,8 @@
1
- require 'action_dispatch/middleware/session/abstract_store'
1
+ # frozen_string_literal: true
2
+
3
+ require "action_dispatch/middleware/session/abstract_store"
2
4
  begin
3
- require 'rack/session/dalli'
5
+ require "rack/session/dalli"
4
6
  rescue LoadError => e
5
7
  $stderr.puts "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install"
6
8
  raise e
@@ -8,6 +10,10 @@ end
8
10
 
9
11
  module ActionDispatch
10
12
  module Session
13
+ # A session store that uses MemCache to implement storage.
14
+ #
15
+ # ==== Options
16
+ # * <tt>expire_after</tt> - The length of time a session will be stored before automatically expiring.
11
17
  class MemCacheStore < Rack::Session::Dalli
12
18
  include Compatibility
13
19
  include StaleSessionCheck
@@ -1,5 +1,7 @@
1
- require 'action_dispatch/http/request'
2
- require 'action_dispatch/middleware/exception_wrapper'
1
+ # frozen_string_literal: true
2
+
3
+ require "action_dispatch/http/request"
4
+ require "action_dispatch/middleware/exception_wrapper"
3
5
 
4
6
  module ActionDispatch
5
7
  # This middleware rescues any exception returned by the application
@@ -8,14 +10,14 @@ module ActionDispatch
8
10
  # The exceptions app should be passed as parameter on initialization
9
11
  # of ShowExceptions. Every time there is an exception, ShowExceptions will
10
12
  # store the exception in env["action_dispatch.exception"], rewrite the
11
- # PATH_INFO to the exception status code and call the rack app.
13
+ # PATH_INFO to the exception status code and call the Rack app.
12
14
  #
13
15
  # If the application returns a "X-Cascade" pass response, this middleware
14
16
  # will send an empty response as result with the correct status code.
15
17
  # If any exception happens inside the exceptions app, this middleware
16
18
  # catches the exceptions and returns a FAILSAFE_RESPONSE.
17
19
  class ShowExceptions
18
- FAILSAFE_RESPONSE = [500, { 'Content-Type' => 'text/plain' },
20
+ FAILSAFE_RESPONSE = [500, { "Content-Type" => "text/plain" },
19
21
  ["500 Internal Server Error\n" \
20
22
  "If you are the administrator of this website, then please read this web " \
21
23
  "application's log file and/or the web server's log file to find out what " \
@@ -27,32 +29,35 @@ module ActionDispatch
27
29
  end
28
30
 
29
31
  def call(env)
32
+ request = ActionDispatch::Request.new env
30
33
  @app.call(env)
31
34
  rescue Exception => exception
32
- if env['action_dispatch.show_exceptions'] == false
33
- raise exception
35
+ if request.show_exceptions?
36
+ render_exception(request, exception)
34
37
  else
35
- render_exception(env, exception)
38
+ raise exception
36
39
  end
37
40
  end
38
41
 
39
42
  private
43
+ def render_exception(request, exception)
44
+ backtrace_cleaner = request.get_header "action_dispatch.backtrace_cleaner"
45
+ wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
46
+ status = wrapper.status_code
47
+ request.set_header "action_dispatch.exception", wrapper.unwrapped_exception
48
+ request.set_header "action_dispatch.original_path", request.path_info
49
+ request.set_header "action_dispatch.original_request_method", request.raw_request_method
50
+ request.path_info = "/#{status}"
51
+ request.request_method = "GET"
52
+ response = @exceptions_app.call(request.env)
53
+ response[1]["X-Cascade"] == "pass" ? pass_response(status) : response
54
+ rescue Exception => failsafe_error
55
+ $stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
56
+ FAILSAFE_RESPONSE
57
+ end
40
58
 
41
- def render_exception(env, exception)
42
- wrapper = ExceptionWrapper.new(env, exception)
43
- status = wrapper.status_code
44
- env["action_dispatch.exception"] = wrapper.exception
45
- env["action_dispatch.original_path"] = env["PATH_INFO"]
46
- env["PATH_INFO"] = "/#{status}"
47
- response = @exceptions_app.call(env)
48
- response[1]['X-Cascade'] == 'pass' ? pass_response(status) : response
49
- rescue Exception => failsafe_error
50
- $stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
51
- FAILSAFE_RESPONSE
52
- end
53
-
54
- def pass_response(status)
55
- [status, {"Content-Type" => "text/html; charset=#{Response.default_charset}", "Content-Length" => "0"}, []]
56
- end
59
+ def pass_response(status)
60
+ [status, { "Content-Type" => "text/html; charset=#{Response.default_charset}", "Content-Length" => "0" }, []]
61
+ end
57
62
  end
58
63
  end
@@ -1,66 +1,122 @@
1
+ # frozen_string_literal: true
2
+
1
3
  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:
7
+ #
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.
13
+ #
14
+ # Requests can opt-out of redirection with +exclude+:
15
+ #
16
+ # config.ssl_options = { redirect: { exclude: -> request { /healthcheck/.match?(request.path) } } }
17
+ #
18
+ # Cookies will not be flagged as secure for excluded requests.
19
+ #
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.
23
+ #
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.
27
+ #
28
+ # Set +config.ssl_options+ with <tt>hsts: { ... }</tt> to configure HSTS:
29
+ #
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).
33
+ #
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
+ #
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+.
44
+ #
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>.
2
49
  class SSL
3
- YEAR = 31536000
50
+ # :stopdoc:
51
+
52
+ # Default to 2 years as recommended on hstspreload.org.
53
+ HSTS_EXPIRES_IN = 63072000
54
+
55
+ PERMANENT_REDIRECT_REQUEST_METHODS = %w[GET HEAD] # :nodoc:
4
56
 
5
57
  def self.default_hsts_options
6
- { :expires => YEAR, :subdomains => false }
58
+ { expires: HSTS_EXPIRES_IN, subdomains: true, preload: false }
7
59
  end
8
60
 
9
- def initialize(app, options = {})
61
+ def initialize(app, redirect: {}, hsts: {}, secure_cookies: true, ssl_default_redirect_status: nil)
10
62
  @app = app
11
63
 
12
- @hsts = options.fetch(:hsts, {})
13
- @hsts = {} if @hsts == true
14
- @hsts = self.class.default_hsts_options.merge(@hsts) if @hsts
64
+ @redirect = redirect
15
65
 
16
- @host = options[:host]
17
- @port = options[:port]
66
+ @exclude = @redirect && @redirect[:exclude] || proc { !@redirect }
67
+ @secure_cookies = secure_cookies
68
+
69
+ @hsts_header = build_hsts_header(normalize_hsts_options(hsts))
70
+ @ssl_default_redirect_status = ssl_default_redirect_status
18
71
  end
19
72
 
20
73
  def call(env)
21
- request = Request.new(env)
74
+ request = Request.new env
22
75
 
23
76
  if request.ssl?
24
- status, headers, body = @app.call(env)
25
- headers.reverse_merge!(hsts_headers)
26
- flag_cookies_as_secure!(headers)
27
- [status, headers, body]
77
+ @app.call(env).tap do |status, headers, body|
78
+ set_hsts_header! headers
79
+ flag_cookies_as_secure! headers if @secure_cookies && !@exclude.call(request)
80
+ end
28
81
  else
29
- redirect_to_https(request)
82
+ return redirect_to_https request unless @exclude.call(request)
83
+ @app.call(env)
30
84
  end
31
85
  end
32
86
 
33
87
  private
34
- def redirect_to_https(request)
35
- host = @host || request.host
36
- port = @port || request.port
37
-
38
- location = "https://#{host}"
39
- location << ":#{port}" if port != 80
40
- location << request.fullpath
41
-
42
- headers = { 'Content-Type' => 'text/html', 'Location' => location }
43
-
44
- [301, headers, []]
88
+ def set_hsts_header!(headers)
89
+ headers["Strict-Transport-Security"] ||= @hsts_header
45
90
  end
46
91
 
47
- # http://tools.ietf.org/html/draft-hodges-strict-transport-sec-02
48
- def hsts_headers
49
- if @hsts
50
- value = "max-age=#{@hsts[:expires].to_i}"
51
- value += "; includeSubDomains" if @hsts[:subdomains]
52
- { 'Strict-Transport-Security' => value }
92
+ def normalize_hsts_options(options)
93
+ case options
94
+ # Explicitly disabling HSTS clears the existing setting from browsers
95
+ # by setting expiry to 0.
96
+ when false
97
+ self.class.default_hsts_options.merge(expires: 0)
98
+ # Default to enabled, with default options.
99
+ when nil, true
100
+ self.class.default_hsts_options
53
101
  else
54
- {}
102
+ self.class.default_hsts_options.merge(options)
55
103
  end
56
104
  end
57
105
 
106
+ # https://tools.ietf.org/html/rfc6797#section-6.1
107
+ def build_hsts_header(hsts)
108
+ value = +"max-age=#{hsts[:expires].to_i}"
109
+ value << "; includeSubDomains" if hsts[:subdomains]
110
+ value << "; preload" if hsts[:preload]
111
+ value
112
+ end
113
+
58
114
  def flag_cookies_as_secure!(headers)
59
- if cookies = headers['Set-Cookie']
115
+ if cookies = headers["Set-Cookie"]
60
116
  cookies = cookies.split("\n")
61
117
 
62
- headers['Set-Cookie'] = cookies.map { |cookie|
63
- if cookie !~ /;\s*secure\s*(;|$)/i
118
+ headers["Set-Cookie"] = cookies.map { |cookie|
119
+ if !/;\s*secure\s*(;|$)/i.match?(cookie)
64
120
  "#{cookie}; secure"
65
121
  else
66
122
  cookie
@@ -68,5 +124,32 @@ module ActionDispatch
68
124
  }.join("\n")
69
125
  end
70
126
  end
127
+
128
+ def redirect_to_https(request)
129
+ [ @redirect.fetch(:status, redirection_status(request)),
130
+ { "Content-Type" => "text/html",
131
+ "Location" => https_location_for(request) },
132
+ (@redirect[:body] || []) ]
133
+ end
134
+
135
+ def redirection_status(request)
136
+ if PERMANENT_REDIRECT_REQUEST_METHODS.include?(request.raw_request_method)
137
+ 301 # Issue a permanent redirect via a GET request.
138
+ elsif @ssl_default_redirect_status
139
+ @ssl_default_redirect_status
140
+ else
141
+ 307 # Issue a fresh request redirect to preserve the HTTP method.
142
+ end
143
+ end
144
+
145
+ def https_location_for(request)
146
+ host = @redirect[:host] || request.host
147
+ port = @redirect[:port] || request.port
148
+
149
+ location = +"https://#{host}"
150
+ location << ":#{port}" if port != 80 && port != 443
151
+ location << request.fullpath
152
+ location
153
+ end
71
154
  end
72
155
  end