actionpack 3.2.22.5 → 5.2.4

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 (271) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +279 -603
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +13 -297
  5. data/lib/abstract_controller/asset_paths.rb +4 -2
  6. data/lib/abstract_controller/base.rb +82 -52
  7. data/lib/abstract_controller/caching/fragments.rb +166 -0
  8. data/lib/abstract_controller/caching.rb +66 -0
  9. data/lib/abstract_controller/callbacks.rb +117 -103
  10. data/lib/abstract_controller/collector.rb +18 -7
  11. data/lib/abstract_controller/error.rb +6 -0
  12. data/lib/abstract_controller/helpers.rb +65 -38
  13. data/lib/abstract_controller/logger.rb +3 -2
  14. data/lib/abstract_controller/railties/routes_helpers.rb +5 -3
  15. data/lib/abstract_controller/rendering.rb +77 -129
  16. data/lib/abstract_controller/translation.rb +21 -3
  17. data/lib/abstract_controller/url_for.rb +9 -7
  18. data/lib/abstract_controller.rb +12 -13
  19. data/lib/action_controller/api/api_rendering.rb +16 -0
  20. data/lib/action_controller/api.rb +149 -0
  21. data/lib/action_controller/base.rb +81 -40
  22. data/lib/action_controller/caching.rb +22 -62
  23. data/lib/action_controller/form_builder.rb +50 -0
  24. data/lib/action_controller/log_subscriber.rb +30 -18
  25. data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
  26. data/lib/action_controller/metal/conditional_get.rb +190 -47
  27. data/lib/action_controller/metal/content_security_policy.rb +52 -0
  28. data/lib/action_controller/metal/cookies.rb +3 -3
  29. data/lib/action_controller/metal/data_streaming.rb +40 -65
  30. data/lib/action_controller/metal/etag_with_flash.rb +18 -0
  31. data/lib/action_controller/metal/etag_with_template_digest.rb +57 -0
  32. data/lib/action_controller/metal/exceptions.rb +19 -12
  33. data/lib/action_controller/metal/flash.rb +42 -9
  34. data/lib/action_controller/metal/force_ssl.rb +79 -19
  35. data/lib/action_controller/metal/head.rb +35 -10
  36. data/lib/action_controller/metal/helpers.rb +31 -21
  37. data/lib/action_controller/metal/http_authentication.rb +182 -134
  38. data/lib/action_controller/metal/implicit_render.rb +62 -8
  39. data/lib/action_controller/metal/instrumentation.rb +28 -26
  40. data/lib/action_controller/metal/live.rb +312 -0
  41. data/lib/action_controller/metal/mime_responds.rb +159 -163
  42. data/lib/action_controller/metal/parameter_encoding.rb +51 -0
  43. data/lib/action_controller/metal/params_wrapper.rb +146 -93
  44. data/lib/action_controller/metal/redirecting.rb +80 -56
  45. data/lib/action_controller/metal/renderers.rb +119 -47
  46. data/lib/action_controller/metal/rendering.rb +89 -32
  47. data/lib/action_controller/metal/request_forgery_protection.rb +373 -41
  48. data/lib/action_controller/metal/rescue.rb +9 -16
  49. data/lib/action_controller/metal/streaming.rb +39 -45
  50. data/lib/action_controller/metal/strong_parameters.rb +1086 -0
  51. data/lib/action_controller/metal/testing.rb +8 -29
  52. data/lib/action_controller/metal/url_for.rb +43 -32
  53. data/lib/action_controller/metal.rb +112 -106
  54. data/lib/action_controller/railtie.rb +56 -18
  55. data/lib/action_controller/railties/helpers.rb +24 -0
  56. data/lib/action_controller/renderer.rb +117 -0
  57. data/lib/action_controller/template_assertions.rb +11 -0
  58. data/lib/action_controller/test_case.rb +402 -347
  59. data/lib/action_controller.rb +31 -30
  60. data/lib/action_dispatch/http/cache.rb +133 -34
  61. data/lib/action_dispatch/http/content_security_policy.rb +272 -0
  62. data/lib/action_dispatch/http/filter_parameters.rb +40 -24
  63. data/lib/action_dispatch/http/filter_redirect.rb +37 -0
  64. data/lib/action_dispatch/http/headers.rb +117 -16
  65. data/lib/action_dispatch/http/mime_negotiation.rb +98 -33
  66. data/lib/action_dispatch/http/mime_type.rb +198 -146
  67. data/lib/action_dispatch/http/mime_types.rb +22 -7
  68. data/lib/action_dispatch/http/parameter_filter.rb +61 -49
  69. data/lib/action_dispatch/http/parameters.rb +94 -51
  70. data/lib/action_dispatch/http/rack_cache.rb +4 -3
  71. data/lib/action_dispatch/http/request.rb +262 -117
  72. data/lib/action_dispatch/http/response.rb +400 -86
  73. data/lib/action_dispatch/http/upload.rb +66 -29
  74. data/lib/action_dispatch/http/url.rb +232 -60
  75. data/lib/action_dispatch/journey/formatter.rb +189 -0
  76. data/lib/action_dispatch/journey/gtg/builder.rb +164 -0
  77. data/lib/action_dispatch/journey/gtg/simulator.rb +41 -0
  78. data/lib/action_dispatch/journey/gtg/transition_table.rb +158 -0
  79. data/lib/action_dispatch/journey/nfa/builder.rb +78 -0
  80. data/lib/action_dispatch/journey/nfa/dot.rb +36 -0
  81. data/lib/action_dispatch/journey/nfa/simulator.rb +49 -0
  82. data/lib/action_dispatch/journey/nfa/transition_table.rb +120 -0
  83. data/lib/action_dispatch/journey/nodes/node.rb +140 -0
  84. data/lib/action_dispatch/journey/parser.rb +199 -0
  85. data/lib/action_dispatch/journey/parser.y +50 -0
  86. data/lib/action_dispatch/journey/parser_extras.rb +31 -0
  87. data/lib/action_dispatch/journey/path/pattern.rb +199 -0
  88. data/lib/action_dispatch/journey/route.rb +203 -0
  89. data/lib/action_dispatch/journey/router/utils.rb +102 -0
  90. data/lib/action_dispatch/journey/router.rb +156 -0
  91. data/lib/action_dispatch/journey/routes.rb +82 -0
  92. data/lib/action_dispatch/journey/scanner.rb +64 -0
  93. data/lib/action_dispatch/journey/visitors.rb +268 -0
  94. data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
  95. data/lib/action_dispatch/journey/visualizer/fsm.js +134 -0
  96. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  97. data/lib/action_dispatch/journey.rb +7 -0
  98. data/lib/action_dispatch/middleware/callbacks.rb +17 -13
  99. data/lib/action_dispatch/middleware/cookies.rb +494 -162
  100. data/lib/action_dispatch/middleware/debug_exceptions.rb +176 -53
  101. data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
  102. data/lib/action_dispatch/middleware/exception_wrapper.rb +103 -38
  103. data/lib/action_dispatch/middleware/executor.rb +21 -0
  104. data/lib/action_dispatch/middleware/flash.rb +128 -91
  105. data/lib/action_dispatch/middleware/public_exceptions.rb +43 -16
  106. data/lib/action_dispatch/middleware/reloader.rb +6 -83
  107. data/lib/action_dispatch/middleware/remote_ip.rb +151 -49
  108. data/lib/action_dispatch/middleware/request_id.rb +19 -15
  109. data/lib/action_dispatch/middleware/session/abstract_store.rb +38 -34
  110. data/lib/action_dispatch/middleware/session/cache_store.rb +14 -9
  111. data/lib/action_dispatch/middleware/session/cookie_store.rb +94 -44
  112. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +15 -4
  113. data/lib/action_dispatch/middleware/show_exceptions.rb +36 -61
  114. data/lib/action_dispatch/middleware/ssl.rb +150 -0
  115. data/lib/action_dispatch/middleware/stack.rb +33 -41
  116. data/lib/action_dispatch/middleware/static.rb +92 -48
  117. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +22 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
  119. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +27 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  121. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +52 -0
  122. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
  123. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +16 -0
  124. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  125. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +21 -0
  126. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +13 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +134 -5
  128. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
  132. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
  136. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +16 -0
  137. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +200 -0
  138. data/lib/action_dispatch/railtie.rb +29 -8
  139. data/lib/action_dispatch/request/session.rb +234 -0
  140. data/lib/action_dispatch/request/utils.rb +78 -0
  141. data/lib/action_dispatch/routing/endpoint.rb +17 -0
  142. data/lib/action_dispatch/routing/inspector.rb +225 -0
  143. data/lib/action_dispatch/routing/mapper.rb +1329 -582
  144. data/lib/action_dispatch/routing/polymorphic_routes.rb +237 -94
  145. data/lib/action_dispatch/routing/redirection.rb +120 -50
  146. data/lib/action_dispatch/routing/route_set.rb +545 -322
  147. data/lib/action_dispatch/routing/routes_proxy.rb +37 -7
  148. data/lib/action_dispatch/routing/url_for.rb +103 -34
  149. data/lib/action_dispatch/routing.rb +66 -99
  150. data/lib/action_dispatch/system_test_case.rb +147 -0
  151. data/lib/action_dispatch/system_testing/browser.rb +49 -0
  152. data/lib/action_dispatch/system_testing/driver.rb +59 -0
  153. data/lib/action_dispatch/system_testing/server.rb +31 -0
  154. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +96 -0
  155. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +31 -0
  156. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +26 -0
  157. data/lib/action_dispatch/testing/assertion_response.rb +47 -0
  158. data/lib/action_dispatch/testing/assertions/response.rb +53 -42
  159. data/lib/action_dispatch/testing/assertions/routing.rb +79 -74
  160. data/lib/action_dispatch/testing/assertions.rb +15 -9
  161. data/lib/action_dispatch/testing/integration.rb +361 -207
  162. data/lib/action_dispatch/testing/request_encoder.rb +55 -0
  163. data/lib/action_dispatch/testing/test_process.rb +28 -19
  164. data/lib/action_dispatch/testing/test_request.rb +30 -33
  165. data/lib/action_dispatch/testing/test_response.rb +35 -11
  166. data/lib/action_dispatch.rb +42 -32
  167. data/lib/action_pack/gem_version.rb +17 -0
  168. data/lib/action_pack/version.rb +7 -7
  169. data/lib/action_pack.rb +4 -2
  170. metadata +116 -175
  171. data/lib/abstract_controller/layouts.rb +0 -423
  172. data/lib/abstract_controller/view_paths.rb +0 -96
  173. data/lib/action_controller/caching/actions.rb +0 -185
  174. data/lib/action_controller/caching/fragments.rb +0 -127
  175. data/lib/action_controller/caching/pages.rb +0 -187
  176. data/lib/action_controller/caching/sweeping.rb +0 -97
  177. data/lib/action_controller/deprecated/integration_test.rb +0 -2
  178. data/lib/action_controller/deprecated/performance_test.rb +0 -1
  179. data/lib/action_controller/deprecated.rb +0 -3
  180. data/lib/action_controller/metal/compatibility.rb +0 -65
  181. data/lib/action_controller/metal/hide_actions.rb +0 -41
  182. data/lib/action_controller/metal/rack_delegation.rb +0 -26
  183. data/lib/action_controller/metal/responder.rb +0 -286
  184. data/lib/action_controller/metal/session_management.rb +0 -14
  185. data/lib/action_controller/middleware.rb +0 -39
  186. data/lib/action_controller/railties/paths.rb +0 -25
  187. data/lib/action_controller/record_identifier.rb +0 -85
  188. data/lib/action_controller/vendor/html-scanner/html/document.rb +0 -68
  189. data/lib/action_controller/vendor/html-scanner/html/node.rb +0 -532
  190. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +0 -177
  191. data/lib/action_controller/vendor/html-scanner/html/selector.rb +0 -830
  192. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +0 -107
  193. data/lib/action_controller/vendor/html-scanner/html/version.rb +0 -11
  194. data/lib/action_controller/vendor/html-scanner.rb +0 -20
  195. data/lib/action_dispatch/middleware/best_standards_support.rb +0 -30
  196. data/lib/action_dispatch/middleware/body_proxy.rb +0 -30
  197. data/lib/action_dispatch/middleware/head.rb +0 -18
  198. data/lib/action_dispatch/middleware/params_parser.rb +0 -75
  199. data/lib/action_dispatch/middleware/rescue.rb +0 -26
  200. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +0 -31
  201. data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +0 -26
  202. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +0 -10
  203. data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +0 -2
  204. data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +0 -15
  205. data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +0 -17
  206. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +0 -2
  207. data/lib/action_dispatch/testing/assertions/dom.rb +0 -37
  208. data/lib/action_dispatch/testing/assertions/selector.rb +0 -435
  209. data/lib/action_dispatch/testing/assertions/tag.rb +0 -138
  210. data/lib/action_dispatch/testing/performance_test.rb +0 -10
  211. data/lib/action_view/asset_paths.rb +0 -142
  212. data/lib/action_view/base.rb +0 -220
  213. data/lib/action_view/buffers.rb +0 -43
  214. data/lib/action_view/context.rb +0 -36
  215. data/lib/action_view/flows.rb +0 -79
  216. data/lib/action_view/helpers/active_model_helper.rb +0 -50
  217. data/lib/action_view/helpers/asset_paths.rb +0 -7
  218. data/lib/action_view/helpers/asset_tag_helper.rb +0 -457
  219. data/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +0 -146
  220. data/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +0 -93
  221. data/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +0 -193
  222. data/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +0 -148
  223. data/lib/action_view/helpers/atom_feed_helper.rb +0 -200
  224. data/lib/action_view/helpers/cache_helper.rb +0 -64
  225. data/lib/action_view/helpers/capture_helper.rb +0 -203
  226. data/lib/action_view/helpers/controller_helper.rb +0 -25
  227. data/lib/action_view/helpers/csrf_helper.rb +0 -32
  228. data/lib/action_view/helpers/date_helper.rb +0 -1062
  229. data/lib/action_view/helpers/debug_helper.rb +0 -40
  230. data/lib/action_view/helpers/form_helper.rb +0 -1486
  231. data/lib/action_view/helpers/form_options_helper.rb +0 -658
  232. data/lib/action_view/helpers/form_tag_helper.rb +0 -685
  233. data/lib/action_view/helpers/javascript_helper.rb +0 -110
  234. data/lib/action_view/helpers/number_helper.rb +0 -622
  235. data/lib/action_view/helpers/output_safety_helper.rb +0 -38
  236. data/lib/action_view/helpers/record_tag_helper.rb +0 -111
  237. data/lib/action_view/helpers/rendering_helper.rb +0 -92
  238. data/lib/action_view/helpers/sanitize_helper.rb +0 -259
  239. data/lib/action_view/helpers/tag_helper.rb +0 -167
  240. data/lib/action_view/helpers/text_helper.rb +0 -426
  241. data/lib/action_view/helpers/translation_helper.rb +0 -91
  242. data/lib/action_view/helpers/url_helper.rb +0 -693
  243. data/lib/action_view/helpers.rb +0 -60
  244. data/lib/action_view/locale/en.yml +0 -160
  245. data/lib/action_view/log_subscriber.rb +0 -28
  246. data/lib/action_view/lookup_context.rb +0 -258
  247. data/lib/action_view/path_set.rb +0 -101
  248. data/lib/action_view/railtie.rb +0 -55
  249. data/lib/action_view/renderer/abstract_renderer.rb +0 -41
  250. data/lib/action_view/renderer/partial_renderer.rb +0 -415
  251. data/lib/action_view/renderer/renderer.rb +0 -61
  252. data/lib/action_view/renderer/streaming_template_renderer.rb +0 -106
  253. data/lib/action_view/renderer/template_renderer.rb +0 -95
  254. data/lib/action_view/template/error.rb +0 -128
  255. data/lib/action_view/template/handlers/builder.rb +0 -26
  256. data/lib/action_view/template/handlers/erb.rb +0 -125
  257. data/lib/action_view/template/handlers.rb +0 -50
  258. data/lib/action_view/template/resolver.rb +0 -298
  259. data/lib/action_view/template/text.rb +0 -30
  260. data/lib/action_view/template.rb +0 -337
  261. data/lib/action_view/test_case.rb +0 -246
  262. data/lib/action_view/testing/resolvers.rb +0 -49
  263. data/lib/action_view.rb +0 -84
  264. data/lib/sprockets/assets.rake +0 -99
  265. data/lib/sprockets/bootstrap.rb +0 -37
  266. data/lib/sprockets/compressors.rb +0 -83
  267. data/lib/sprockets/helpers/isolated_helper.rb +0 -13
  268. data/lib/sprockets/helpers/rails_helper.rb +0 -182
  269. data/lib/sprockets/helpers.rb +0 -6
  270. data/lib/sprockets/railtie.rb +0 -62
  271. data/lib/sprockets/static_compiler.rb +0 -56
@@ -1,65 +1,66 @@
1
- require 'abstract_controller'
2
- require 'action_dispatch'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/rails"
4
+ require "abstract_controller"
5
+ require "action_dispatch"
6
+ require "action_controller/metal/live"
7
+ require "action_controller/metal/strong_parameters"
3
8
 
4
9
  module ActionController
5
10
  extend ActiveSupport::Autoload
6
11
 
12
+ autoload :API
7
13
  autoload :Base
8
- autoload :Caching
9
14
  autoload :Metal
10
15
  autoload :Middleware
16
+ autoload :Renderer
17
+ autoload :FormBuilder
18
+
19
+ eager_autoload do
20
+ autoload :Caching
21
+ end
11
22
 
12
23
  autoload_under "metal" do
13
- autoload :Compatibility
14
24
  autoload :ConditionalGet
25
+ autoload :ContentSecurityPolicy
15
26
  autoload :Cookies
16
27
  autoload :DataStreaming
28
+ autoload :EtagWithTemplateDigest
29
+ autoload :EtagWithFlash
17
30
  autoload :Flash
18
31
  autoload :ForceSSL
19
32
  autoload :Head
20
33
  autoload :Helpers
21
- autoload :HideActions
22
34
  autoload :HttpAuthentication
35
+ autoload :BasicImplicitRender
23
36
  autoload :ImplicitRender
24
37
  autoload :Instrumentation
25
38
  autoload :MimeResponds
26
39
  autoload :ParamsWrapper
27
- autoload :RackDelegation
28
40
  autoload :Redirecting
29
41
  autoload :Renderers
30
42
  autoload :Rendering
31
43
  autoload :RequestForgeryProtection
32
44
  autoload :Rescue
33
- autoload :Responder
34
- autoload :SessionManagement
35
45
  autoload :Streaming
46
+ autoload :StrongParameters
47
+ autoload :ParameterEncoding
36
48
  autoload :Testing
37
49
  autoload :UrlFor
38
50
  end
39
51
 
40
- autoload :Integration, 'action_controller/deprecated/integration_test'
41
- autoload :IntegrationTest, 'action_controller/deprecated/integration_test'
42
- autoload :PerformanceTest, 'action_controller/deprecated/performance_test'
43
- autoload :UrlWriter, 'action_controller/deprecated'
44
- autoload :Routing, 'action_controller/deprecated'
45
- autoload :TestCase, 'action_controller/test_case'
46
- autoload :TemplateAssertions, 'action_controller/test_case'
47
-
48
- eager_autoload do
49
- autoload :RecordIdentifier
52
+ autoload_under "api" do
53
+ autoload :ApiRendering
50
54
  end
51
- end
52
55
 
53
- # All of these simply register additional autoloads
54
- require 'action_view'
55
- require 'action_controller/vendor/html-scanner'
56
+ autoload :TestCase, "action_controller/test_case"
57
+ autoload :TemplateAssertions, "action_controller/test_case"
58
+ end
56
59
 
57
60
  # Common Active Support usage in Action Controller
58
- require 'active_support/concern'
59
- require 'active_support/core_ext/class/attribute_accessors'
60
- require 'active_support/core_ext/load_error'
61
- require 'active_support/core_ext/module/attr_internal'
62
- require 'active_support/core_ext/module/delegation'
63
- require 'active_support/core_ext/name_error'
64
- require 'active_support/core_ext/uri'
65
- require 'active_support/inflector'
61
+ require "active_support/core_ext/module/attribute_accessors"
62
+ require "active_support/core_ext/load_error"
63
+ require "active_support/core_ext/module/attr_internal"
64
+ require "active_support/core_ext/name_error"
65
+ require "active_support/core_ext/uri"
66
+ require "active_support/inflector"
@@ -1,21 +1,24 @@
1
- require 'active_support/core_ext/object/blank'
1
+ # frozen_string_literal: true
2
2
 
3
3
  module ActionDispatch
4
4
  module Http
5
5
  module Cache
6
6
  module Request
7
-
8
- HTTP_IF_MODIFIED_SINCE = 'HTTP_IF_MODIFIED_SINCE'.freeze
9
- HTTP_IF_NONE_MATCH = 'HTTP_IF_NONE_MATCH'.freeze
7
+ HTTP_IF_MODIFIED_SINCE = "HTTP_IF_MODIFIED_SINCE".freeze
8
+ HTTP_IF_NONE_MATCH = "HTTP_IF_NONE_MATCH".freeze
10
9
 
11
10
  def if_modified_since
12
- if since = env[HTTP_IF_MODIFIED_SINCE]
11
+ if since = get_header(HTTP_IF_MODIFIED_SINCE)
13
12
  Time.rfc2822(since) rescue nil
14
13
  end
15
14
  end
16
15
 
17
16
  def if_none_match
18
- env[HTTP_IF_NONE_MATCH]
17
+ get_header HTTP_IF_NONE_MATCH
18
+ end
19
+
20
+ def if_none_match_etags
21
+ if_none_match ? if_none_match.split(/\s*,\s*/) : []
19
22
  end
20
23
 
21
24
  def not_modified?(modified_at)
@@ -23,7 +26,10 @@ module ActionDispatch
23
26
  end
24
27
 
25
28
  def etag_matches?(etag)
26
- if_none_match && if_none_match == etag
29
+ if etag
30
+ validators = if_none_match_etags
31
+ validators.include?(etag) || validators.include?("*")
32
+ end
27
33
  end
28
34
 
29
35
  # Check response freshness (Last-Modified and ETag) against request
@@ -43,50 +49,121 @@ module ActionDispatch
43
49
  end
44
50
 
45
51
  module Response
46
- attr_reader :cache_control, :etag
47
- alias :etag? :etag
52
+ attr_reader :cache_control
48
53
 
49
54
  def last_modified
50
- if last = headers[LAST_MODIFIED]
55
+ if last = get_header(LAST_MODIFIED)
51
56
  Time.httpdate(last)
52
57
  end
53
58
  end
54
59
 
55
60
  def last_modified?
56
- headers.include?(LAST_MODIFIED)
61
+ has_header? LAST_MODIFIED
57
62
  end
58
63
 
59
64
  def last_modified=(utc_time)
60
- headers[LAST_MODIFIED] = utc_time.httpdate
65
+ set_header LAST_MODIFIED, utc_time.httpdate
66
+ end
67
+
68
+ def date
69
+ if date_header = get_header(DATE)
70
+ Time.httpdate(date_header)
71
+ end
72
+ end
73
+
74
+ def date?
75
+ has_header? DATE
76
+ end
77
+
78
+ def date=(utc_time)
79
+ set_header DATE, utc_time.httpdate
61
80
  end
62
81
 
63
- def etag=(etag)
64
- key = ActiveSupport::Cache.expand_cache_key(etag)
65
- @etag = self[ETAG] = %("#{Digest::MD5.hexdigest(key)}")
82
+ # This method sets a weak ETag validator on the response so browsers
83
+ # and proxies may cache the response, keyed on the ETag. On subsequent
84
+ # requests, the If-None-Match header is set to the cached ETag. If it
85
+ # matches the current ETag, we can return a 304 Not Modified response
86
+ # with no body, letting the browser or proxy know that their cache is
87
+ # current. Big savings in request time and network bandwidth.
88
+ #
89
+ # Weak ETags are considered to be semantically equivalent but not
90
+ # byte-for-byte identical. This is perfect for browser caching of HTML
91
+ # pages where we don't care about exact equality, just what the user
92
+ # is viewing.
93
+ #
94
+ # Strong ETags are considered byte-for-byte identical. They allow a
95
+ # browser or proxy cache to support Range requests, useful for paging
96
+ # through a PDF file or scrubbing through a video. Some CDNs only
97
+ # support strong ETags and will ignore weak ETags entirely.
98
+ #
99
+ # Weak ETags are what we almost always need, so they're the default.
100
+ # Check out #strong_etag= to provide a strong ETag validator.
101
+ def etag=(weak_validators)
102
+ self.weak_etag = weak_validators
103
+ end
104
+
105
+ def weak_etag=(weak_validators)
106
+ set_header "ETag", generate_weak_etag(weak_validators)
107
+ end
108
+
109
+ def strong_etag=(strong_validators)
110
+ set_header "ETag", generate_strong_etag(strong_validators)
111
+ end
112
+
113
+ def etag?; etag; end
114
+
115
+ # True if an ETag is set and it's a weak validator (preceded with W/)
116
+ def weak_etag?
117
+ etag? && etag.starts_with?('W/"')
118
+ end
119
+
120
+ # True if an ETag is set and it isn't a weak validator (not preceded with W/)
121
+ def strong_etag?
122
+ etag? && !weak_etag?
66
123
  end
67
124
 
68
125
  private
69
126
 
127
+ DATE = "Date".freeze
70
128
  LAST_MODIFIED = "Last-Modified".freeze
71
- ETAG = "ETag".freeze
72
- CACHE_CONTROL = "Cache-Control".freeze
129
+ SPECIAL_KEYS = Set.new(%w[extras no-cache max-age public private must-revalidate])
73
130
 
74
- def prepare_cache_control!
75
- @cache_control = {}
76
- @etag = self[ETAG]
131
+ def generate_weak_etag(validators)
132
+ "W/#{generate_strong_etag(validators)}"
133
+ end
77
134
 
78
- if cache_control = self[CACHE_CONTROL]
79
- cache_control.split(/,\s*/).each do |segment|
80
- first, last = segment.split("=")
81
- @cache_control[first.to_sym] = last || true
82
- end
135
+ def generate_strong_etag(validators)
136
+ %("#{ActiveSupport::Digest.hexdigest(ActiveSupport::Cache.expand_cache_key(validators))}")
137
+ end
138
+
139
+ def cache_control_segments
140
+ if cache_control = _cache_control
141
+ cache_control.delete(" ").split(",")
142
+ else
143
+ []
83
144
  end
84
145
  end
85
146
 
86
- def handle_conditional_get!
87
- if etag? || last_modified? || !@cache_control.empty?
88
- set_conditional_cache_control!
147
+ def cache_control_headers
148
+ cache_control = {}
149
+
150
+ cache_control_segments.each do |segment|
151
+ directive, argument = segment.split("=", 2)
152
+
153
+ if SPECIAL_KEYS.include? directive
154
+ key = directive.tr("-", "_")
155
+ cache_control[key.to_sym] = argument || true
156
+ else
157
+ cache_control[:extras] ||= []
158
+ cache_control[:extras] << segment
159
+ end
89
160
  end
161
+
162
+ cache_control
163
+ end
164
+
165
+ def prepare_cache_control!
166
+ @cache_control = cache_control_headers
90
167
  end
91
168
 
92
169
  DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate".freeze
@@ -95,15 +172,37 @@ module ActionDispatch
95
172
  PRIVATE = "private".freeze
96
173
  MUST_REVALIDATE = "must-revalidate".freeze
97
174
 
98
- def set_conditional_cache_control!
99
- return if self[CACHE_CONTROL].present?
175
+ def handle_conditional_get!
176
+ # Normally default cache control setting is handled by ETag
177
+ # middleware. But, if an etag is already set, the middleware
178
+ # defaults to `no-cache` unless a default `Cache-Control` value is
179
+ # previously set. So, set a default one here.
180
+ if (etag? || last_modified?) && !self._cache_control
181
+ self._cache_control = DEFAULT_CACHE_CONTROL
182
+ end
183
+ end
184
+
185
+ def merge_and_normalize_cache_control!(cache_control)
186
+ control = {}
187
+ cc_headers = cache_control_headers
188
+ if extras = cc_headers.delete(:extras)
189
+ cache_control[:extras] ||= []
190
+ cache_control[:extras] += extras
191
+ cache_control[:extras].uniq!
192
+ end
100
193
 
101
- control = @cache_control
194
+ control.merge! cc_headers
195
+ control.merge! cache_control
102
196
 
103
197
  if control.empty?
104
- headers[CACHE_CONTROL] = DEFAULT_CACHE_CONTROL
198
+ # Let middleware handle default behavior
105
199
  elsif control[:no_cache]
106
- headers[CACHE_CONTROL] = NO_CACHE
200
+ options = []
201
+ options << PUBLIC if control[:public]
202
+ options << NO_CACHE
203
+ options.concat(control[:extras]) if control[:extras]
204
+
205
+ self._cache_control = options.join(", ")
107
206
  else
108
207
  extras = control[:extras]
109
208
  max_age = control[:max_age]
@@ -114,7 +213,7 @@ module ActionDispatch
114
213
  options << MUST_REVALIDATE if control[:must_revalidate]
115
214
  options.concat(extras) if extras
116
215
 
117
- headers[CACHE_CONTROL] = options.join(", ")
216
+ self._cache_control = options.join(", ")
118
217
  end
119
218
  end
120
219
  end
@@ -0,0 +1,272 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/object/deep_dup"
4
+
5
+ module ActionDispatch #:nodoc:
6
+ class ContentSecurityPolicy
7
+ class Middleware
8
+ CONTENT_TYPE = "Content-Type".freeze
9
+ POLICY = "Content-Security-Policy".freeze
10
+ POLICY_REPORT_ONLY = "Content-Security-Policy-Report-Only".freeze
11
+
12
+ def initialize(app)
13
+ @app = app
14
+ end
15
+
16
+ def call(env)
17
+ request = ActionDispatch::Request.new env
18
+ _, headers, _ = response = @app.call(env)
19
+
20
+ return response unless html_response?(headers)
21
+ return response if policy_present?(headers)
22
+
23
+ if policy = request.content_security_policy
24
+ nonce = request.content_security_policy_nonce
25
+ context = request.controller_instance || request
26
+ headers[header_name(request)] = policy.build(context, nonce)
27
+ end
28
+
29
+ response
30
+ end
31
+
32
+ private
33
+
34
+ def html_response?(headers)
35
+ if content_type = headers[CONTENT_TYPE]
36
+ content_type =~ /html/
37
+ end
38
+ end
39
+
40
+ def header_name(request)
41
+ if request.content_security_policy_report_only
42
+ POLICY_REPORT_ONLY
43
+ else
44
+ POLICY
45
+ end
46
+ end
47
+
48
+ def policy_present?(headers)
49
+ headers[POLICY] || headers[POLICY_REPORT_ONLY]
50
+ end
51
+ end
52
+
53
+ module Request
54
+ POLICY = "action_dispatch.content_security_policy".freeze
55
+ POLICY_REPORT_ONLY = "action_dispatch.content_security_policy_report_only".freeze
56
+ NONCE_GENERATOR = "action_dispatch.content_security_policy_nonce_generator".freeze
57
+ NONCE = "action_dispatch.content_security_policy_nonce".freeze
58
+
59
+ def content_security_policy
60
+ get_header(POLICY)
61
+ end
62
+
63
+ def content_security_policy=(policy)
64
+ set_header(POLICY, policy)
65
+ end
66
+
67
+ def content_security_policy_report_only
68
+ get_header(POLICY_REPORT_ONLY)
69
+ end
70
+
71
+ def content_security_policy_report_only=(value)
72
+ set_header(POLICY_REPORT_ONLY, value)
73
+ end
74
+
75
+ def content_security_policy_nonce_generator
76
+ get_header(NONCE_GENERATOR)
77
+ end
78
+
79
+ def content_security_policy_nonce_generator=(generator)
80
+ set_header(NONCE_GENERATOR, generator)
81
+ end
82
+
83
+ def content_security_policy_nonce
84
+ if content_security_policy_nonce_generator
85
+ if nonce = get_header(NONCE)
86
+ nonce
87
+ else
88
+ set_header(NONCE, generate_content_security_policy_nonce)
89
+ end
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ def generate_content_security_policy_nonce
96
+ content_security_policy_nonce_generator.call(self)
97
+ end
98
+ end
99
+
100
+ MAPPINGS = {
101
+ self: "'self'",
102
+ unsafe_eval: "'unsafe-eval'",
103
+ unsafe_inline: "'unsafe-inline'",
104
+ none: "'none'",
105
+ http: "http:",
106
+ https: "https:",
107
+ data: "data:",
108
+ mediastream: "mediastream:",
109
+ blob: "blob:",
110
+ filesystem: "filesystem:",
111
+ report_sample: "'report-sample'",
112
+ strict_dynamic: "'strict-dynamic'",
113
+ ws: "ws:",
114
+ wss: "wss:"
115
+ }.freeze
116
+
117
+ DIRECTIVES = {
118
+ base_uri: "base-uri",
119
+ child_src: "child-src",
120
+ connect_src: "connect-src",
121
+ default_src: "default-src",
122
+ font_src: "font-src",
123
+ form_action: "form-action",
124
+ frame_ancestors: "frame-ancestors",
125
+ frame_src: "frame-src",
126
+ img_src: "img-src",
127
+ manifest_src: "manifest-src",
128
+ media_src: "media-src",
129
+ object_src: "object-src",
130
+ script_src: "script-src",
131
+ style_src: "style-src",
132
+ worker_src: "worker-src"
133
+ }.freeze
134
+
135
+ NONCE_DIRECTIVES = %w[script-src].freeze
136
+
137
+ private_constant :MAPPINGS, :DIRECTIVES, :NONCE_DIRECTIVES
138
+
139
+ attr_reader :directives
140
+
141
+ def initialize
142
+ @directives = {}
143
+ yield self if block_given?
144
+ end
145
+
146
+ def initialize_copy(other)
147
+ @directives = other.directives.deep_dup
148
+ end
149
+
150
+ DIRECTIVES.each do |name, directive|
151
+ define_method(name) do |*sources|
152
+ if sources.first
153
+ @directives[directive] = apply_mappings(sources)
154
+ else
155
+ @directives.delete(directive)
156
+ end
157
+ end
158
+ end
159
+
160
+ def block_all_mixed_content(enabled = true)
161
+ if enabled
162
+ @directives["block-all-mixed-content"] = true
163
+ else
164
+ @directives.delete("block-all-mixed-content")
165
+ end
166
+ end
167
+
168
+ def plugin_types(*types)
169
+ if types.first
170
+ @directives["plugin-types"] = types
171
+ else
172
+ @directives.delete("plugin-types")
173
+ end
174
+ end
175
+
176
+ def report_uri(uri)
177
+ @directives["report-uri"] = [uri]
178
+ end
179
+
180
+ def require_sri_for(*types)
181
+ if types.first
182
+ @directives["require-sri-for"] = types
183
+ else
184
+ @directives.delete("require-sri-for")
185
+ end
186
+ end
187
+
188
+ def sandbox(*values)
189
+ if values.empty?
190
+ @directives["sandbox"] = true
191
+ elsif values.first
192
+ @directives["sandbox"] = values
193
+ else
194
+ @directives.delete("sandbox")
195
+ end
196
+ end
197
+
198
+ def upgrade_insecure_requests(enabled = true)
199
+ if enabled
200
+ @directives["upgrade-insecure-requests"] = true
201
+ else
202
+ @directives.delete("upgrade-insecure-requests")
203
+ end
204
+ end
205
+
206
+ def build(context = nil, nonce = nil)
207
+ build_directives(context, nonce).compact.join("; ")
208
+ end
209
+
210
+ private
211
+ def apply_mappings(sources)
212
+ sources.map do |source|
213
+ case source
214
+ when Symbol
215
+ apply_mapping(source)
216
+ when String, Proc
217
+ source
218
+ else
219
+ raise ArgumentError, "Invalid content security policy source: #{source.inspect}"
220
+ end
221
+ end
222
+ end
223
+
224
+ def apply_mapping(source)
225
+ MAPPINGS.fetch(source) do
226
+ raise ArgumentError, "Unknown content security policy source mapping: #{source.inspect}"
227
+ end
228
+ end
229
+
230
+ def build_directives(context, nonce)
231
+ @directives.map do |directive, sources|
232
+ if sources.is_a?(Array)
233
+ if nonce && nonce_directive?(directive)
234
+ "#{directive} #{build_directive(sources, context).join(' ')} 'nonce-#{nonce}'"
235
+ else
236
+ "#{directive} #{build_directive(sources, context).join(' ')}"
237
+ end
238
+ elsif sources
239
+ directive
240
+ else
241
+ nil
242
+ end
243
+ end
244
+ end
245
+
246
+ def build_directive(sources, context)
247
+ sources.map { |source| resolve_source(source, context) }
248
+ end
249
+
250
+ def resolve_source(source, context)
251
+ case source
252
+ when String
253
+ source
254
+ when Symbol
255
+ source.to_s
256
+ when Proc
257
+ if context.nil?
258
+ raise RuntimeError, "Missing context for the dynamic content security policy source: #{source.inspect}"
259
+ else
260
+ resolved = context.instance_exec(&source)
261
+ resolved.is_a?(Symbol) ? apply_mapping(resolved) : resolved
262
+ end
263
+ else
264
+ raise RuntimeError, "Unexpected content security policy source: #{source.inspect}"
265
+ end
266
+ end
267
+
268
+ def nonce_directive?(directive)
269
+ NONCE_DIRECTIVES.include?(directive)
270
+ end
271
+ end
272
+ end