omg-actionpack 8.0.0.alpha1

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 (187) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +129 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +57 -0
  5. data/lib/abstract_controller/asset_paths.rb +14 -0
  6. data/lib/abstract_controller/base.rb +299 -0
  7. data/lib/abstract_controller/caching/fragments.rb +149 -0
  8. data/lib/abstract_controller/caching.rb +68 -0
  9. data/lib/abstract_controller/callbacks.rb +265 -0
  10. data/lib/abstract_controller/collector.rb +44 -0
  11. data/lib/abstract_controller/deprecator.rb +9 -0
  12. data/lib/abstract_controller/error.rb +8 -0
  13. data/lib/abstract_controller/helpers.rb +243 -0
  14. data/lib/abstract_controller/logger.rb +16 -0
  15. data/lib/abstract_controller/railties/routes_helpers.rb +25 -0
  16. data/lib/abstract_controller/rendering.rb +126 -0
  17. data/lib/abstract_controller/translation.rb +42 -0
  18. data/lib/abstract_controller/url_for.rb +37 -0
  19. data/lib/abstract_controller.rb +36 -0
  20. data/lib/action_controller/api/api_rendering.rb +18 -0
  21. data/lib/action_controller/api.rb +155 -0
  22. data/lib/action_controller/base.rb +332 -0
  23. data/lib/action_controller/caching.rb +49 -0
  24. data/lib/action_controller/deprecator.rb +9 -0
  25. data/lib/action_controller/form_builder.rb +55 -0
  26. data/lib/action_controller/log_subscriber.rb +96 -0
  27. data/lib/action_controller/metal/allow_browser.rb +123 -0
  28. data/lib/action_controller/metal/basic_implicit_render.rb +17 -0
  29. data/lib/action_controller/metal/conditional_get.rb +341 -0
  30. data/lib/action_controller/metal/content_security_policy.rb +86 -0
  31. data/lib/action_controller/metal/cookies.rb +20 -0
  32. data/lib/action_controller/metal/data_streaming.rb +154 -0
  33. data/lib/action_controller/metal/default_headers.rb +21 -0
  34. data/lib/action_controller/metal/etag_with_flash.rb +22 -0
  35. data/lib/action_controller/metal/etag_with_template_digest.rb +59 -0
  36. data/lib/action_controller/metal/exceptions.rb +106 -0
  37. data/lib/action_controller/metal/flash.rb +67 -0
  38. data/lib/action_controller/metal/head.rb +67 -0
  39. data/lib/action_controller/metal/helpers.rb +129 -0
  40. data/lib/action_controller/metal/http_authentication.rb +565 -0
  41. data/lib/action_controller/metal/implicit_render.rb +67 -0
  42. data/lib/action_controller/metal/instrumentation.rb +120 -0
  43. data/lib/action_controller/metal/live.rb +398 -0
  44. data/lib/action_controller/metal/logging.rb +22 -0
  45. data/lib/action_controller/metal/mime_responds.rb +337 -0
  46. data/lib/action_controller/metal/parameter_encoding.rb +84 -0
  47. data/lib/action_controller/metal/params_wrapper.rb +312 -0
  48. data/lib/action_controller/metal/permissions_policy.rb +38 -0
  49. data/lib/action_controller/metal/rate_limiting.rb +62 -0
  50. data/lib/action_controller/metal/redirecting.rb +251 -0
  51. data/lib/action_controller/metal/renderers.rb +181 -0
  52. data/lib/action_controller/metal/rendering.rb +260 -0
  53. data/lib/action_controller/metal/request_forgery_protection.rb +667 -0
  54. data/lib/action_controller/metal/rescue.rb +33 -0
  55. data/lib/action_controller/metal/streaming.rb +183 -0
  56. data/lib/action_controller/metal/strong_parameters.rb +1546 -0
  57. data/lib/action_controller/metal/testing.rb +25 -0
  58. data/lib/action_controller/metal/url_for.rb +65 -0
  59. data/lib/action_controller/metal.rb +339 -0
  60. data/lib/action_controller/railtie.rb +149 -0
  61. data/lib/action_controller/railties/helpers.rb +26 -0
  62. data/lib/action_controller/renderer.rb +161 -0
  63. data/lib/action_controller/template_assertions.rb +13 -0
  64. data/lib/action_controller/test_case.rb +691 -0
  65. data/lib/action_controller.rb +80 -0
  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 +249 -0
  69. data/lib/action_dispatch/http/content_disposition.rb +47 -0
  70. data/lib/action_dispatch/http/content_security_policy.rb +365 -0
  71. data/lib/action_dispatch/http/filter_parameters.rb +80 -0
  72. data/lib/action_dispatch/http/filter_redirect.rb +50 -0
  73. data/lib/action_dispatch/http/headers.rb +134 -0
  74. data/lib/action_dispatch/http/mime_negotiation.rb +187 -0
  75. data/lib/action_dispatch/http/mime_type.rb +389 -0
  76. data/lib/action_dispatch/http/mime_types.rb +54 -0
  77. data/lib/action_dispatch/http/parameters.rb +119 -0
  78. data/lib/action_dispatch/http/permissions_policy.rb +189 -0
  79. data/lib/action_dispatch/http/rack_cache.rb +67 -0
  80. data/lib/action_dispatch/http/request.rb +498 -0
  81. data/lib/action_dispatch/http/response.rb +556 -0
  82. data/lib/action_dispatch/http/upload.rb +107 -0
  83. data/lib/action_dispatch/http/url.rb +344 -0
  84. data/lib/action_dispatch/journey/formatter.rb +226 -0
  85. data/lib/action_dispatch/journey/gtg/builder.rb +149 -0
  86. data/lib/action_dispatch/journey/gtg/simulator.rb +50 -0
  87. data/lib/action_dispatch/journey/gtg/transition_table.rb +217 -0
  88. data/lib/action_dispatch/journey/nfa/dot.rb +27 -0
  89. data/lib/action_dispatch/journey/nodes/node.rb +208 -0
  90. data/lib/action_dispatch/journey/parser.rb +103 -0
  91. data/lib/action_dispatch/journey/path/pattern.rb +209 -0
  92. data/lib/action_dispatch/journey/route.rb +189 -0
  93. data/lib/action_dispatch/journey/router/utils.rb +105 -0
  94. data/lib/action_dispatch/journey/router.rb +151 -0
  95. data/lib/action_dispatch/journey/routes.rb +82 -0
  96. data/lib/action_dispatch/journey/scanner.rb +70 -0
  97. data/lib/action_dispatch/journey/visitors.rb +267 -0
  98. data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
  99. data/lib/action_dispatch/journey/visualizer/fsm.js +159 -0
  100. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  101. data/lib/action_dispatch/journey.rb +7 -0
  102. data/lib/action_dispatch/log_subscriber.rb +25 -0
  103. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  104. data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
  105. data/lib/action_dispatch/middleware/callbacks.rb +38 -0
  106. data/lib/action_dispatch/middleware/cookies.rb +719 -0
  107. data/lib/action_dispatch/middleware/debug_exceptions.rb +206 -0
  108. data/lib/action_dispatch/middleware/debug_locks.rb +129 -0
  109. data/lib/action_dispatch/middleware/debug_view.rb +73 -0
  110. data/lib/action_dispatch/middleware/exception_wrapper.rb +350 -0
  111. data/lib/action_dispatch/middleware/executor.rb +32 -0
  112. data/lib/action_dispatch/middleware/flash.rb +318 -0
  113. data/lib/action_dispatch/middleware/host_authorization.rb +171 -0
  114. data/lib/action_dispatch/middleware/public_exceptions.rb +64 -0
  115. data/lib/action_dispatch/middleware/reloader.rb +16 -0
  116. data/lib/action_dispatch/middleware/remote_ip.rb +199 -0
  117. data/lib/action_dispatch/middleware/request_id.rb +50 -0
  118. data/lib/action_dispatch/middleware/server_timing.rb +78 -0
  119. data/lib/action_dispatch/middleware/session/abstract_store.rb +112 -0
  120. data/lib/action_dispatch/middleware/session/cache_store.rb +66 -0
  121. data/lib/action_dispatch/middleware/session/cookie_store.rb +129 -0
  122. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +34 -0
  123. data/lib/action_dispatch/middleware/show_exceptions.rb +88 -0
  124. data/lib/action_dispatch/middleware/ssl.rb +180 -0
  125. data/lib/action_dispatch/middleware/stack.rb +194 -0
  126. data/lib/action_dispatch/middleware/static.rb +192 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  128. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +17 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
  132. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +36 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +62 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
  136. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +12 -0
  137. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +9 -0
  138. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +35 -0
  139. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  140. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
  141. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +16 -0
  142. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +284 -0
  143. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +23 -0
  144. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  145. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
  146. data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
  147. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
  148. data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
  149. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
  150. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
  151. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
  152. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
  153. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +19 -0
  154. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +232 -0
  155. data/lib/action_dispatch/railtie.rb +77 -0
  156. data/lib/action_dispatch/request/session.rb +283 -0
  157. data/lib/action_dispatch/request/utils.rb +109 -0
  158. data/lib/action_dispatch/routing/endpoint.rb +19 -0
  159. data/lib/action_dispatch/routing/inspector.rb +323 -0
  160. data/lib/action_dispatch/routing/mapper.rb +2372 -0
  161. data/lib/action_dispatch/routing/polymorphic_routes.rb +363 -0
  162. data/lib/action_dispatch/routing/redirection.rb +218 -0
  163. data/lib/action_dispatch/routing/route_set.rb +958 -0
  164. data/lib/action_dispatch/routing/routes_proxy.rb +66 -0
  165. data/lib/action_dispatch/routing/url_for.rb +244 -0
  166. data/lib/action_dispatch/routing.rb +262 -0
  167. data/lib/action_dispatch/system_test_case.rb +206 -0
  168. data/lib/action_dispatch/system_testing/browser.rb +75 -0
  169. data/lib/action_dispatch/system_testing/driver.rb +85 -0
  170. data/lib/action_dispatch/system_testing/server.rb +33 -0
  171. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +164 -0
  172. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +23 -0
  173. data/lib/action_dispatch/testing/assertion_response.rb +48 -0
  174. data/lib/action_dispatch/testing/assertions/response.rb +114 -0
  175. data/lib/action_dispatch/testing/assertions/routing.rb +343 -0
  176. data/lib/action_dispatch/testing/assertions.rb +25 -0
  177. data/lib/action_dispatch/testing/integration.rb +694 -0
  178. data/lib/action_dispatch/testing/request_encoder.rb +60 -0
  179. data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
  180. data/lib/action_dispatch/testing/test_process.rb +57 -0
  181. data/lib/action_dispatch/testing/test_request.rb +73 -0
  182. data/lib/action_dispatch/testing/test_response.rb +58 -0
  183. data/lib/action_dispatch.rb +147 -0
  184. data/lib/action_pack/gem_version.rb +19 -0
  185. data/lib/action_pack/version.rb +12 -0
  186. data/lib/action_pack.rb +27 -0
  187. metadata +375 -0
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "action_dispatch/middleware/session/abstract_store"
6
+
7
+ module ActionDispatch
8
+ module Session
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.
21
+ #
22
+ class CacheStore < AbstractSecureStore
23
+ def initialize(app, options = {})
24
+ @cache = options[:cache] || Rails.cache
25
+ options[:expire_after] ||= @cache.options[:expires_in]
26
+ super
27
+ end
28
+
29
+ # Get a session from the cache.
30
+ def find_session(env, sid)
31
+ unless sid && (session = get_session_with_fallback(sid))
32
+ sid, session = generate_sid, {}
33
+ end
34
+ [sid, session]
35
+ end
36
+
37
+ # Set a session in the cache.
38
+ def write_session(env, sid, session, options)
39
+ key = cache_key(sid.private_id)
40
+ if session
41
+ @cache.write(key, session, expires_in: options[:expire_after])
42
+ else
43
+ @cache.delete(key)
44
+ end
45
+ sid
46
+ end
47
+
48
+ # Remove a session from the cache.
49
+ def delete_session(env, sid, options)
50
+ @cache.delete(cache_key(sid.private_id))
51
+ @cache.delete(cache_key(sid.public_id))
52
+ generate_sid
53
+ end
54
+
55
+ private
56
+ # Turn the session id into a cache key.
57
+ def cache_key(id)
58
+ "_session_id:#{id}"
59
+ end
60
+
61
+ def get_session_with_fallback(sid)
62
+ @cache.read(cache_key(sid.private_id)) || @cache.read(cache_key(sid.public_id))
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "active_support/core_ext/hash/keys"
6
+ require "action_dispatch/middleware/session/abstract_store"
7
+ require "rack/session/cookie"
8
+
9
+ module ActionDispatch
10
+ module Session
11
+ # # Action Dispatch Session CookieStore
12
+ #
13
+ # This cookie-based session store is the Rails default. It is dramatically
14
+ # faster than the alternatives.
15
+ #
16
+ # Sessions typically contain at most a user ID and flash message; both fit
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.
19
+ #
20
+ # The cookie jar used for storage is automatically configured to be the best
21
+ # possible option given your application's configuration.
22
+ #
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
25
+ # be altered or read by users. This is the default starting in Rails 4.
26
+ #
27
+ # Configure your session store in an initializer:
28
+ #
29
+ # Rails.application.config.session_store :cookie_store, key: '_your_app_session'
30
+ #
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.
35
+ #
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.
38
+ #
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
42
+ # applications or JavaScript before changing it.
43
+ #
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:
47
+ #
48
+ # Rails.application.config.session_store :cookie_store, expire_after: 14.days
49
+ #
50
+ # would set the session cookie to expire automatically 14 days after creation.
51
+ # Other useful options include `:key`, `:secure`, `:httponly`, and `:same_site`.
52
+ class CookieStore < AbstractSecureStore
53
+ class SessionId < DelegateClass(Rack::Session::SessionId)
54
+ attr_reader :cookie_value
55
+
56
+ def initialize(session_id, cookie_value = {})
57
+ super(session_id)
58
+ @cookie_value = cookie_value
59
+ end
60
+ end
61
+
62
+ DEFAULT_SAME_SITE = proc { |request| request.cookies_same_site_protection } # :nodoc:
63
+
64
+ def initialize(app, options = {})
65
+ options[:cookie_only] = true
66
+ options[:same_site] = DEFAULT_SAME_SITE if !options.key?(:same_site)
67
+ super
68
+ end
69
+
70
+ def delete_session(req, session_id, options)
71
+ new_sid = generate_sid unless options[:drop]
72
+ # Reset hash and Assign the new session id
73
+ req.set_header("action_dispatch.request.unsigned_session_cookie", new_sid ? { "session_id" => new_sid.public_id } : {})
74
+ new_sid
75
+ end
76
+
77
+ def load_session(req)
78
+ stale_session_check! do
79
+ data = unpacked_cookie_data(req)
80
+ data = persistent_session_id!(data)
81
+ [Rack::Session::SessionId.new(data["session_id"]), data]
82
+ end
83
+ end
84
+
85
+ private
86
+ def extract_session_id(req)
87
+ stale_session_check! do
88
+ sid = unpacked_cookie_data(req)["session_id"]
89
+ sid && Rack::Session::SessionId.new(sid)
90
+ end
91
+ end
92
+
93
+ def unpacked_cookie_data(req)
94
+ req.fetch_header("action_dispatch.request.unsigned_session_cookie") do |k|
95
+ v = stale_session_check! do
96
+ if data = get_cookie(req)
97
+ data.stringify_keys!
98
+ end
99
+ data || {}
100
+ end
101
+ req.set_header k, v
102
+ end
103
+ end
104
+
105
+ def persistent_session_id!(data, sid = nil)
106
+ data ||= {}
107
+ data["session_id"] ||= sid || generate_sid.public_id
108
+ data
109
+ end
110
+
111
+ def write_session(req, sid, session_data, options)
112
+ session_data["session_id"] = sid.public_id
113
+ SessionId.new(sid, session_data)
114
+ end
115
+
116
+ def set_cookie(request, session_id, cookie)
117
+ cookie_jar(request)[@key] = cookie
118
+ end
119
+
120
+ def get_cookie(req)
121
+ cookie_jar(req)[@key]
122
+ end
123
+
124
+ def cookie_jar(request)
125
+ request.cookie_jar.signed_or_encrypted
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "action_dispatch/middleware/session/abstract_store"
6
+ begin
7
+ require "rack/session/dalli"
8
+ rescue LoadError => e
9
+ warn "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install"
10
+ raise e
11
+ end
12
+
13
+ module ActionDispatch
14
+ module Session
15
+ # # Action Dispatch Session MemCacheStore
16
+ #
17
+ # A session store that uses MemCache to implement storage.
18
+ #
19
+ # #### Options
20
+ # * `expire_after` - The length of time a session will be stored before
21
+ # automatically expiring.
22
+ #
23
+ class MemCacheStore < Rack::Session::Dalli
24
+ include Compatibility
25
+ include StaleSessionCheck
26
+ include SessionObject
27
+
28
+ def initialize(app, options = {})
29
+ options[:expire_after] ||= options[:expires]
30
+ super
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "action_dispatch/middleware/exception_wrapper"
6
+
7
+ module ActionDispatch
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.
12
+ #
13
+ # The exceptions app should be passed as a parameter on initialization of
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.
17
+ #
18
+ # In Rails applications, the exceptions app can be configured with
19
+ # `config.exceptions_app`, which defaults to ActionDispatch::PublicExceptions.
20
+ #
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.
25
+ class ShowExceptions
26
+ def initialize(app, exceptions_app)
27
+ @app = app
28
+ @exceptions_app = exceptions_app
29
+ end
30
+
31
+ def call(env)
32
+ @app.call(env)
33
+ rescue Exception => 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)
42
+ else
43
+ raise exception
44
+ end
45
+ end
46
+
47
+ private
48
+ def render_exception(request, wrapper)
49
+ status = wrapper.status_code
50
+ request.set_header "action_dispatch.original_path", request.path_info
51
+ request.set_header "action_dispatch.original_request_method", request.raw_request_method
52
+ fallback_to_html_format_if_invalid_mime_type(request)
53
+ request.path_info = "/#{status}"
54
+ request.request_method = "GET"
55
+ response = @exceptions_app.call(request.env)
56
+ response[1][Constants::X_CASCADE] == "pass" ? pass_response(status) : response
57
+ rescue Exception => failsafe_error
58
+ $stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
59
+
60
+ [500, { Rack::CONTENT_TYPE => "text/plain; charset=utf-8" },
61
+ ["500 Internal Server Error\n" \
62
+ "If you are the administrator of this website, then please read this web " \
63
+ "application's log file and/or the web server's log file to find out what " \
64
+ "went wrong."]]
65
+ end
66
+
67
+ def fallback_to_html_format_if_invalid_mime_type(request)
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
81
+ end
82
+
83
+ def pass_response(status)
84
+ [status, { Rack::CONTENT_TYPE => "text/html; charset=#{Response.default_charset}",
85
+ Rack::CONTENT_LENGTH => "0" }, []]
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,180 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ module ActionDispatch
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:
15
+ #
16
+ # config.ssl_options = { redirect: { host: "secure.widgets.com", port: 8080 }`
17
+ #
18
+ # Or set `redirect: false` to disable redirection.
19
+ #
20
+ # Requests can opt-out of redirection with `exclude`:
21
+ #
22
+ # config.ssl_options = { redirect: { exclude: -> request { /healthcheck/.match?(request.path) } } }
23
+ #
24
+ # Cookies will not be flagged as secure for excluded requests.
25
+ #
26
+ # When proxying through a load balancer that terminates SSL, the forwarded
27
+ # request will appear as though it's HTTP instead of HTTPS to the application.
28
+ # This makes redirects and cookie security target HTTP instead of HTTPS.
29
+ # To make the server assume that the proxy already terminated SSL, and
30
+ # that the request really is HTTPS, set `config.assume_ssl` to `true`:
31
+ #
32
+ # config.assume_ssl = true
33
+ #
34
+ # 2. **Secure cookies**: Sets the `secure` flag on cookies to tell browsers
35
+ # they must not be sent along with `http://` requests. Enabled by default.
36
+ # Set `config.ssl_options` with `secure_cookies: false` to disable this
37
+ # feature.
38
+ #
39
+ # 3. **HTTP Strict Transport Security (HSTS)**: Tells the browser to remember
40
+ # this site as TLS-only and automatically redirect non-TLS requests. Enabled
41
+ # by default. Configure `config.ssl_options` with `hsts: false` to disable.
42
+ #
43
+ # Set `config.ssl_options` with `hsts: { ... }` to configure HSTS:
44
+ #
45
+ # * `expires`: How long, in seconds, these settings will stick. The
46
+ # minimum required to qualify for browser preload lists is 1 year.
47
+ # Defaults to 2 years (recommended).
48
+ #
49
+ # * `subdomains`: Set to `true` to tell the browser to apply these
50
+ # settings to all subdomains. This protects your cookies from
51
+ # interception by a vulnerable site on a subdomain. Defaults to `true`.
52
+ #
53
+ # * `preload`: Advertise that this site may be included in browsers'
54
+ # preloaded HSTS lists. HSTS protects your site on every visit *except
55
+ # the first visit* since it hasn't seen your HSTS header yet. To close
56
+ # this gap, browser vendors include a baked-in list of HSTS-enabled
57
+ # sites. Go to https://hstspreload.org to submit your site for
58
+ # inclusion. Defaults to `false`.
59
+ #
60
+ #
61
+ # To turn off HSTS, omitting the header is not enough. Browsers will
62
+ # remember the original HSTS directive until it expires. Instead, use the
63
+ # header to tell browsers to expire HSTS immediately. Setting `hsts: false`
64
+ # is a shortcut for `hsts: { expires: 0 }`.
65
+ #
66
+ class SSL
67
+ # :stopdoc: Default to 2 years as recommended on hstspreload.org.
68
+ HSTS_EXPIRES_IN = 63072000
69
+
70
+ PERMANENT_REDIRECT_REQUEST_METHODS = %w[GET HEAD] # :nodoc:
71
+
72
+ def self.default_hsts_options
73
+ { expires: HSTS_EXPIRES_IN, subdomains: true, preload: false }
74
+ end
75
+
76
+ def initialize(app, redirect: {}, hsts: {}, secure_cookies: true, ssl_default_redirect_status: nil)
77
+ @app = app
78
+
79
+ @redirect = redirect
80
+
81
+ @exclude = @redirect && @redirect[:exclude] || proc { !@redirect }
82
+ @secure_cookies = secure_cookies
83
+
84
+ @hsts_header = build_hsts_header(normalize_hsts_options(hsts))
85
+ @ssl_default_redirect_status = ssl_default_redirect_status
86
+ end
87
+
88
+ def call(env)
89
+ request = Request.new env
90
+
91
+ if request.ssl?
92
+ @app.call(env).tap do |status, headers, body|
93
+ set_hsts_header! headers
94
+ flag_cookies_as_secure! headers if @secure_cookies && !@exclude.call(request)
95
+ end
96
+ else
97
+ return redirect_to_https request unless @exclude.call(request)
98
+ @app.call(env)
99
+ end
100
+ end
101
+
102
+ private
103
+ def set_hsts_header!(headers)
104
+ headers[Constants::STRICT_TRANSPORT_SECURITY] ||= @hsts_header
105
+ end
106
+
107
+ def normalize_hsts_options(options)
108
+ case options
109
+ # Explicitly disabling HSTS clears the existing setting from browsers by setting
110
+ # expiry to 0.
111
+ when false
112
+ self.class.default_hsts_options.merge(expires: 0)
113
+ # Default to enabled, with default options.
114
+ when nil, true
115
+ self.class.default_hsts_options
116
+ else
117
+ self.class.default_hsts_options.merge(options)
118
+ end
119
+ end
120
+
121
+ # https://tools.ietf.org/html/rfc6797#section-6.1
122
+ def build_hsts_header(hsts)
123
+ value = +"max-age=#{hsts[:expires].to_i}"
124
+ value << "; includeSubDomains" if hsts[:subdomains]
125
+ value << "; preload" if hsts[:preload]
126
+ value
127
+ end
128
+
129
+ def flag_cookies_as_secure!(headers)
130
+ cookies = headers[Rack::SET_COOKIE]
131
+ return unless cookies
132
+
133
+ if Gem::Version.new(Rack::RELEASE) < Gem::Version.new("3")
134
+ cookies = cookies.split("\n")
135
+ headers[Rack::SET_COOKIE] = cookies.map { |cookie|
136
+ if !/;\s*secure\s*(;|$)/i.match?(cookie)
137
+ "#{cookie}; secure"
138
+ else
139
+ cookie
140
+ end
141
+ }.join("\n")
142
+ else
143
+ headers[Rack::SET_COOKIE] = Array(cookies).map do |cookie|
144
+ if !/;\s*secure\s*(;|$)/i.match?(cookie)
145
+ "#{cookie}; secure"
146
+ else
147
+ cookie
148
+ end
149
+ end
150
+ end
151
+ end
152
+
153
+ def redirect_to_https(request)
154
+ [ @redirect.fetch(:status, redirection_status(request)),
155
+ { Rack::CONTENT_TYPE => "text/html; charset=utf-8",
156
+ Constants::LOCATION => https_location_for(request) },
157
+ (@redirect[:body] || []) ]
158
+ end
159
+
160
+ def redirection_status(request)
161
+ if PERMANENT_REDIRECT_REQUEST_METHODS.include?(request.raw_request_method)
162
+ 301 # Issue a permanent redirect via a GET request.
163
+ elsif @ssl_default_redirect_status
164
+ @ssl_default_redirect_status
165
+ else
166
+ 307 # Issue a fresh request redirect to preserve the HTTP method.
167
+ end
168
+ end
169
+
170
+ def https_location_for(request)
171
+ host = @redirect[:host] || request.host
172
+ port = @redirect[:port] || request.port
173
+
174
+ location = +"https://#{host}"
175
+ location << ":#{port}" if port != 80 && port != 443
176
+ location << request.fullpath
177
+ location
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "active_support/inflector/methods"
6
+ require "active_support/dependencies"
7
+
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.
14
+ class MiddlewareStack
15
+ class Middleware
16
+ attr_reader :args, :block, :klass
17
+
18
+ def initialize(klass, args, block)
19
+ @klass = klass
20
+ @args = args
21
+ @block = block
22
+ end
23
+
24
+ def name; klass.name; end
25
+
26
+ def ==(middleware)
27
+ case middleware
28
+ when Middleware
29
+ klass == middleware.klass
30
+ when Module
31
+ klass == middleware
32
+ end
33
+ end
34
+
35
+ def inspect
36
+ if klass.is_a?(Module)
37
+ klass.to_s
38
+ else
39
+ klass.class.to_s
40
+ end
41
+ end
42
+
43
+ def build(app)
44
+ klass.new(app, *args, &block)
45
+ end
46
+
47
+ def build_instrumented(app)
48
+ InstrumentationProxy.new(build(app), inspect)
49
+ end
50
+ end
51
+
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.
54
+ class InstrumentationProxy
55
+ EVENT_NAME = "process_middleware.action_dispatch"
56
+
57
+ def initialize(middleware, class_name)
58
+ @middleware = middleware
59
+
60
+ @payload = {
61
+ middleware: class_name,
62
+ }
63
+ end
64
+
65
+ def call(env)
66
+ ActiveSupport::Notifications.instrument(EVENT_NAME, @payload) do
67
+ @middleware.call(env)
68
+ end
69
+ end
70
+ end
71
+
72
+ include Enumerable
73
+
74
+ attr_accessor :middlewares
75
+
76
+ def initialize(*args)
77
+ @middlewares = []
78
+ yield(self) if block_given?
79
+ end
80
+
81
+ def each(&block)
82
+ @middlewares.each(&block)
83
+ end
84
+
85
+ def size
86
+ middlewares.size
87
+ end
88
+
89
+ def last
90
+ middlewares.last
91
+ end
92
+
93
+ def [](i)
94
+ middlewares[i]
95
+ end
96
+
97
+ def unshift(klass, *args, &block)
98
+ middlewares.unshift(build_middleware(klass, args, block))
99
+ end
100
+ ruby2_keywords(:unshift)
101
+
102
+ def initialize_copy(other)
103
+ self.middlewares = other.middlewares.dup
104
+ end
105
+
106
+ def insert(index, klass, *args, &block)
107
+ index = assert_index(index, :before)
108
+ middlewares.insert(index, build_middleware(klass, args, block))
109
+ end
110
+ ruby2_keywords(:insert)
111
+
112
+ alias_method :insert_before, :insert
113
+
114
+ def insert_after(index, *args, &block)
115
+ index = assert_index(index, :after)
116
+ insert(index + 1, *args, &block)
117
+ end
118
+ ruby2_keywords(:insert_after)
119
+
120
+ def swap(target, *args, &block)
121
+ index = assert_index(target, :before)
122
+ insert(index, *args, &block)
123
+ middlewares.delete_at(index + 1)
124
+ end
125
+ ruby2_keywords(:swap)
126
+
127
+ # Deletes a middleware from the middleware stack.
128
+ #
129
+ # Returns the array of middlewares not including the deleted item, or returns
130
+ # nil if the target is not found.
131
+ def delete(target)
132
+ middlewares.reject! { |m| m.name == target.name }
133
+ end
134
+
135
+ # Deletes a middleware from the middleware stack.
136
+ #
137
+ # Returns the array of middlewares not including the deleted item, or raises
138
+ # `RuntimeError` if the target is not found.
139
+ def delete!(target)
140
+ delete(target) || (raise "No such middleware to remove: #{target.inspect}")
141
+ end
142
+
143
+ def move(target, source)
144
+ source_index = assert_index(source, :before)
145
+ source_middleware = middlewares.delete_at(source_index)
146
+
147
+ target_index = assert_index(target, :before)
148
+ middlewares.insert(target_index, source_middleware)
149
+ end
150
+
151
+ alias_method :move_before, :move
152
+
153
+ def move_after(target, source)
154
+ source_index = assert_index(source, :after)
155
+ source_middleware = middlewares.delete_at(source_index)
156
+
157
+ target_index = assert_index(target, :after)
158
+ middlewares.insert(target_index + 1, source_middleware)
159
+ end
160
+
161
+ def use(klass, *args, &block)
162
+ middlewares.push(build_middleware(klass, args, block))
163
+ end
164
+ ruby2_keywords(:use)
165
+
166
+ def build(app = nil, &block)
167
+ instrumenting = ActiveSupport::Notifications.notifier.listening?(InstrumentationProxy::EVENT_NAME)
168
+ middlewares.freeze.reverse.inject(app || block) do |a, e|
169
+ if instrumenting
170
+ e.build_instrumented(a)
171
+ else
172
+ e.build(a)
173
+ end
174
+ end
175
+ end
176
+
177
+ private
178
+ def assert_index(index, where)
179
+ i = index.is_a?(Integer) ? index : index_of(index)
180
+ raise "No such middleware to insert #{where}: #{index.inspect}" unless i
181
+ i
182
+ end
183
+
184
+ def build_middleware(klass, args, block)
185
+ Middleware.new(klass, args, block)
186
+ end
187
+
188
+ def index_of(klass)
189
+ middlewares.index do |m|
190
+ m.name == klass.name
191
+ end
192
+ end
193
+ end
194
+ end