actionpack 4.2.10 → 7.2.0.rc1

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.

Potentially problematic release.


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

Files changed (202) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +86 -600
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +13 -14
  5. data/lib/abstract_controller/asset_paths.rb +5 -1
  6. data/lib/abstract_controller/base.rb +166 -136
  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 +126 -57
  10. data/lib/abstract_controller/collector.rb +13 -15
  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 +181 -132
  14. data/lib/abstract_controller/logger.rb +5 -1
  15. data/lib/abstract_controller/railties/routes_helpers.rb +10 -3
  16. data/lib/abstract_controller/rendering.rb +56 -56
  17. data/lib/abstract_controller/translation.rb +29 -15
  18. data/lib/abstract_controller/url_for.rb +15 -11
  19. data/lib/abstract_controller.rb +21 -5
  20. data/lib/action_controller/api/api_rendering.rb +18 -0
  21. data/lib/action_controller/api.rb +154 -0
  22. data/lib/action_controller/base.rb +219 -155
  23. data/lib/action_controller/caching.rb +28 -68
  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 +35 -22
  27. data/lib/action_controller/metal/allow_browser.rb +119 -0
  28. data/lib/action_controller/metal/basic_implicit_render.rb +17 -0
  29. data/lib/action_controller/metal/conditional_get.rb +259 -122
  30. data/lib/action_controller/metal/content_security_policy.rb +86 -0
  31. data/lib/action_controller/metal/cookies.rb +9 -5
  32. data/lib/action_controller/metal/data_streaming.rb +87 -104
  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 +35 -26
  36. data/lib/action_controller/metal/exceptions.rb +71 -24
  37. data/lib/action_controller/metal/flash.rb +26 -19
  38. data/lib/action_controller/metal/head.rb +45 -36
  39. data/lib/action_controller/metal/helpers.rb +80 -64
  40. data/lib/action_controller/metal/http_authentication.rb +297 -244
  41. data/lib/action_controller/metal/implicit_render.rb +57 -9
  42. data/lib/action_controller/metal/instrumentation.rb +76 -64
  43. data/lib/action_controller/metal/live.rb +238 -176
  44. data/lib/action_controller/metal/logging.rb +22 -0
  45. data/lib/action_controller/metal/mime_responds.rb +177 -166
  46. data/lib/action_controller/metal/parameter_encoding.rb +84 -0
  47. data/lib/action_controller/metal/params_wrapper.rb +145 -118
  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 +203 -64
  51. data/lib/action_controller/metal/renderers.rb +108 -65
  52. data/lib/action_controller/metal/rendering.rb +216 -56
  53. data/lib/action_controller/metal/request_forgery_protection.rb +496 -163
  54. data/lib/action_controller/metal/rescue.rb +19 -21
  55. data/lib/action_controller/metal/streaming.rb +179 -138
  56. data/lib/action_controller/metal/strong_parameters.rb +1058 -382
  57. data/lib/action_controller/metal/testing.rb +11 -17
  58. data/lib/action_controller/metal/url_for.rb +37 -21
  59. data/lib/action_controller/metal.rb +236 -138
  60. data/lib/action_controller/railtie.rb +89 -11
  61. data/lib/action_controller/railties/helpers.rb +5 -1
  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 +425 -497
  65. data/lib/action_controller.rb +44 -22
  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 +119 -63
  69. data/lib/action_dispatch/http/content_disposition.rb +47 -0
  70. data/lib/action_dispatch/http/content_security_policy.rb +364 -0
  71. data/lib/action_dispatch/http/filter_parameters.rb +36 -34
  72. data/lib/action_dispatch/http/filter_redirect.rb +24 -12
  73. data/lib/action_dispatch/http/headers.rb +66 -31
  74. data/lib/action_dispatch/http/mime_negotiation.rb +106 -75
  75. data/lib/action_dispatch/http/mime_type.rb +196 -136
  76. data/lib/action_dispatch/http/mime_types.rb +25 -7
  77. data/lib/action_dispatch/http/parameters.rb +97 -45
  78. data/lib/action_dispatch/http/permissions_policy.rb +187 -0
  79. data/lib/action_dispatch/http/rack_cache.rb +6 -0
  80. data/lib/action_dispatch/http/request.rb +299 -170
  81. data/lib/action_dispatch/http/response.rb +311 -160
  82. data/lib/action_dispatch/http/upload.rb +52 -23
  83. data/lib/action_dispatch/http/url.rb +201 -125
  84. data/lib/action_dispatch/journey/formatter.rb +110 -50
  85. data/lib/action_dispatch/journey/gtg/builder.rb +37 -50
  86. data/lib/action_dispatch/journey/gtg/simulator.rb +20 -17
  87. data/lib/action_dispatch/journey/gtg/transition_table.rb +96 -36
  88. data/lib/action_dispatch/journey/nfa/dot.rb +5 -14
  89. data/lib/action_dispatch/journey/nodes/node.rb +100 -20
  90. data/lib/action_dispatch/journey/parser.rb +19 -17
  91. data/lib/action_dispatch/journey/parser.y +4 -3
  92. data/lib/action_dispatch/journey/parser_extras.rb +14 -4
  93. data/lib/action_dispatch/journey/path/pattern.rb +79 -63
  94. data/lib/action_dispatch/journey/route.rb +108 -44
  95. data/lib/action_dispatch/journey/router/utils.rb +41 -29
  96. data/lib/action_dispatch/journey/router.rb +64 -57
  97. data/lib/action_dispatch/journey/routes.rb +23 -21
  98. data/lib/action_dispatch/journey/scanner.rb +28 -17
  99. data/lib/action_dispatch/journey/visitors.rb +100 -54
  100. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  101. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  102. data/lib/action_dispatch/journey.rb +7 -5
  103. data/lib/action_dispatch/log_subscriber.rb +25 -0
  104. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  105. data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
  106. data/lib/action_dispatch/middleware/callbacks.rb +7 -6
  107. data/lib/action_dispatch/middleware/cookies.rb +471 -328
  108. data/lib/action_dispatch/middleware/debug_exceptions.rb +149 -66
  109. data/lib/action_dispatch/middleware/debug_locks.rb +129 -0
  110. data/lib/action_dispatch/middleware/debug_view.rb +73 -0
  111. data/lib/action_dispatch/middleware/exception_wrapper.rb +275 -73
  112. data/lib/action_dispatch/middleware/executor.rb +32 -0
  113. data/lib/action_dispatch/middleware/flash.rb +143 -101
  114. data/lib/action_dispatch/middleware/host_authorization.rb +171 -0
  115. data/lib/action_dispatch/middleware/public_exceptions.rb +36 -27
  116. data/lib/action_dispatch/middleware/reloader.rb +10 -92
  117. data/lib/action_dispatch/middleware/remote_ip.rb +133 -107
  118. data/lib/action_dispatch/middleware/request_id.rb +29 -15
  119. data/lib/action_dispatch/middleware/server_timing.rb +78 -0
  120. data/lib/action_dispatch/middleware/session/abstract_store.rb +49 -27
  121. data/lib/action_dispatch/middleware/session/cache_store.rb +33 -16
  122. data/lib/action_dispatch/middleware/session/cookie_store.rb +86 -80
  123. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +15 -3
  124. data/lib/action_dispatch/middleware/show_exceptions.rb +66 -36
  125. data/lib/action_dispatch/middleware/ssl.rb +134 -36
  126. data/lib/action_dispatch/middleware/stack.rb +109 -44
  127. data/lib/action_dispatch/middleware/static.rb +159 -90
  128. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +7 -24
  132. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  133. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +36 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +46 -36
  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 +26 -7
  139. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +3 -3
  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 +139 -15
  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 +6 -6
  146. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +7 -7
  147. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +9 -9
  148. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  149. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
  150. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  151. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +7 -4
  152. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +125 -93
  153. data/lib/action_dispatch/railtie.rb +44 -16
  154. data/lib/action_dispatch/request/session.rb +159 -69
  155. data/lib/action_dispatch/request/utils.rb +97 -23
  156. data/lib/action_dispatch/routing/endpoint.rb +11 -2
  157. data/lib/action_dispatch/routing/inspector.rb +195 -106
  158. data/lib/action_dispatch/routing/mapper.rb +1338 -955
  159. data/lib/action_dispatch/routing/polymorphic_routes.rb +234 -201
  160. data/lib/action_dispatch/routing/redirection.rb +78 -51
  161. data/lib/action_dispatch/routing/route_set.rb +460 -374
  162. data/lib/action_dispatch/routing/routes_proxy.rb +36 -12
  163. data/lib/action_dispatch/routing/url_for.rb +172 -124
  164. data/lib/action_dispatch/routing.rb +159 -158
  165. data/lib/action_dispatch/system_test_case.rb +206 -0
  166. data/lib/action_dispatch/system_testing/browser.rb +84 -0
  167. data/lib/action_dispatch/system_testing/driver.rb +85 -0
  168. data/lib/action_dispatch/system_testing/server.rb +33 -0
  169. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +164 -0
  170. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +23 -0
  171. data/lib/action_dispatch/testing/assertion_response.rb +48 -0
  172. data/lib/action_dispatch/testing/assertions/response.rb +71 -39
  173. data/lib/action_dispatch/testing/assertions/routing.rb +228 -103
  174. data/lib/action_dispatch/testing/assertions.rb +9 -6
  175. data/lib/action_dispatch/testing/integration.rb +486 -306
  176. data/lib/action_dispatch/testing/request_encoder.rb +60 -0
  177. data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
  178. data/lib/action_dispatch/testing/test_process.rb +35 -22
  179. data/lib/action_dispatch/testing/test_request.rb +29 -34
  180. data/lib/action_dispatch/testing/test_response.rb +48 -15
  181. data/lib/action_dispatch.rb +82 -40
  182. data/lib/action_pack/gem_version.rb +8 -4
  183. data/lib/action_pack/version.rb +6 -2
  184. data/lib/action_pack.rb +21 -18
  185. metadata +146 -56
  186. data/lib/action_controller/caching/fragments.rb +0 -103
  187. data/lib/action_controller/metal/force_ssl.rb +0 -97
  188. data/lib/action_controller/metal/hide_actions.rb +0 -40
  189. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  190. data/lib/action_controller/middleware.rb +0 -39
  191. data/lib/action_controller/model_naming.rb +0 -12
  192. data/lib/action_dispatch/http/parameter_filter.rb +0 -72
  193. data/lib/action_dispatch/journey/backwards.rb +0 -5
  194. data/lib/action_dispatch/journey/nfa/builder.rb +0 -76
  195. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
  196. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -163
  197. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  198. data/lib/action_dispatch/middleware/params_parser.rb +0 -60
  199. data/lib/action_dispatch/middleware/templates/rescues/_source.erb +0 -27
  200. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  201. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  202. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -0,0 +1,364 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "active_support/core_ext/object/deep_dup"
6
+ require "active_support/core_ext/array/wrap"
7
+
8
+ module ActionDispatch # :nodoc:
9
+ # # Action Dispatch Content Security Policy
10
+ #
11
+ # Configures the HTTP [Content-Security-Policy]
12
+ # (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy)
13
+ # response header to help protect against XSS and
14
+ # injection attacks.
15
+ #
16
+ # Example global policy:
17
+ #
18
+ # Rails.application.config.content_security_policy do |policy|
19
+ # policy.default_src :self, :https
20
+ # policy.font_src :self, :https, :data
21
+ # policy.img_src :self, :https, :data
22
+ # policy.object_src :none
23
+ # policy.script_src :self, :https
24
+ # policy.style_src :self, :https
25
+ #
26
+ # # Specify URI for violation reports
27
+ # policy.report_uri "/csp-violation-report-endpoint"
28
+ # end
29
+ class ContentSecurityPolicy
30
+ class Middleware
31
+ def initialize(app)
32
+ @app = app
33
+ end
34
+
35
+ def call(env)
36
+ status, headers, _ = response = @app.call(env)
37
+
38
+ # Returning CSP headers with a 304 Not Modified is harmful, since nonces in the
39
+ # new CSP headers might not match nonces in the cached HTML.
40
+ return response if status == 304
41
+
42
+ return response if policy_present?(headers)
43
+
44
+ request = ActionDispatch::Request.new env
45
+
46
+ if policy = request.content_security_policy
47
+ nonce = request.content_security_policy_nonce
48
+ nonce_directives = request.content_security_policy_nonce_directives
49
+ context = request.controller_instance || request
50
+ headers[header_name(request)] = policy.build(context, nonce, nonce_directives)
51
+ end
52
+
53
+ response
54
+ end
55
+
56
+ private
57
+ def header_name(request)
58
+ if request.content_security_policy_report_only
59
+ ActionDispatch::Constants::CONTENT_SECURITY_POLICY_REPORT_ONLY
60
+ else
61
+ ActionDispatch::Constants::CONTENT_SECURITY_POLICY
62
+ end
63
+ end
64
+
65
+ def policy_present?(headers)
66
+ headers[ActionDispatch::Constants::CONTENT_SECURITY_POLICY] ||
67
+ headers[ActionDispatch::Constants::CONTENT_SECURITY_POLICY_REPORT_ONLY]
68
+ end
69
+ end
70
+
71
+ module Request
72
+ POLICY = "action_dispatch.content_security_policy"
73
+ POLICY_REPORT_ONLY = "action_dispatch.content_security_policy_report_only"
74
+ NONCE_GENERATOR = "action_dispatch.content_security_policy_nonce_generator"
75
+ NONCE = "action_dispatch.content_security_policy_nonce"
76
+ NONCE_DIRECTIVES = "action_dispatch.content_security_policy_nonce_directives"
77
+
78
+ def content_security_policy
79
+ get_header(POLICY)
80
+ end
81
+
82
+ def content_security_policy=(policy)
83
+ set_header(POLICY, policy)
84
+ end
85
+
86
+ def content_security_policy_report_only
87
+ get_header(POLICY_REPORT_ONLY)
88
+ end
89
+
90
+ def content_security_policy_report_only=(value)
91
+ set_header(POLICY_REPORT_ONLY, value)
92
+ end
93
+
94
+ def content_security_policy_nonce_generator
95
+ get_header(NONCE_GENERATOR)
96
+ end
97
+
98
+ def content_security_policy_nonce_generator=(generator)
99
+ set_header(NONCE_GENERATOR, generator)
100
+ end
101
+
102
+ def content_security_policy_nonce_directives
103
+ get_header(NONCE_DIRECTIVES)
104
+ end
105
+
106
+ def content_security_policy_nonce_directives=(generator)
107
+ set_header(NONCE_DIRECTIVES, generator)
108
+ end
109
+
110
+ def content_security_policy_nonce
111
+ if content_security_policy_nonce_generator
112
+ if nonce = get_header(NONCE)
113
+ nonce
114
+ else
115
+ set_header(NONCE, generate_content_security_policy_nonce)
116
+ end
117
+ end
118
+ end
119
+
120
+ private
121
+ def generate_content_security_policy_nonce
122
+ content_security_policy_nonce_generator.call(self)
123
+ end
124
+ end
125
+
126
+ MAPPINGS = {
127
+ self: "'self'",
128
+ unsafe_eval: "'unsafe-eval'",
129
+ unsafe_hashes: "'unsafe-hashes'",
130
+ unsafe_inline: "'unsafe-inline'",
131
+ none: "'none'",
132
+ http: "http:",
133
+ https: "https:",
134
+ data: "data:",
135
+ mediastream: "mediastream:",
136
+ allow_duplicates: "'allow-duplicates'",
137
+ blob: "blob:",
138
+ filesystem: "filesystem:",
139
+ report_sample: "'report-sample'",
140
+ script: "'script'",
141
+ strict_dynamic: "'strict-dynamic'",
142
+ ws: "ws:",
143
+ wss: "wss:"
144
+ }.freeze
145
+
146
+ DIRECTIVES = {
147
+ base_uri: "base-uri",
148
+ child_src: "child-src",
149
+ connect_src: "connect-src",
150
+ default_src: "default-src",
151
+ font_src: "font-src",
152
+ form_action: "form-action",
153
+ frame_ancestors: "frame-ancestors",
154
+ frame_src: "frame-src",
155
+ img_src: "img-src",
156
+ manifest_src: "manifest-src",
157
+ media_src: "media-src",
158
+ object_src: "object-src",
159
+ prefetch_src: "prefetch-src",
160
+ require_trusted_types_for: "require-trusted-types-for",
161
+ script_src: "script-src",
162
+ script_src_attr: "script-src-attr",
163
+ script_src_elem: "script-src-elem",
164
+ style_src: "style-src",
165
+ style_src_attr: "style-src-attr",
166
+ style_src_elem: "style-src-elem",
167
+ trusted_types: "trusted-types",
168
+ worker_src: "worker-src"
169
+ }.freeze
170
+
171
+ DEFAULT_NONCE_DIRECTIVES = %w[script-src style-src].freeze
172
+
173
+ private_constant :MAPPINGS, :DIRECTIVES, :DEFAULT_NONCE_DIRECTIVES
174
+
175
+ attr_reader :directives
176
+
177
+ def initialize
178
+ @directives = {}
179
+ yield self if block_given?
180
+ end
181
+
182
+ def initialize_copy(other)
183
+ @directives = other.directives.deep_dup
184
+ end
185
+
186
+ DIRECTIVES.each do |name, directive|
187
+ define_method(name) do |*sources|
188
+ if sources.first
189
+ @directives[directive] = apply_mappings(sources)
190
+ else
191
+ @directives.delete(directive)
192
+ end
193
+ end
194
+ end
195
+
196
+ # Specify whether to prevent the user agent from loading any assets over HTTP
197
+ # when the page uses HTTPS:
198
+ #
199
+ # policy.block_all_mixed_content
200
+ #
201
+ # Pass `false` to allow it again:
202
+ #
203
+ # policy.block_all_mixed_content false
204
+ #
205
+ def block_all_mixed_content(enabled = true)
206
+ if enabled
207
+ @directives["block-all-mixed-content"] = true
208
+ else
209
+ @directives.delete("block-all-mixed-content")
210
+ end
211
+ end
212
+
213
+ # Restricts the set of plugins that can be embedded:
214
+ #
215
+ # policy.plugin_types "application/x-shockwave-flash"
216
+ #
217
+ # Leave empty to allow all plugins:
218
+ #
219
+ # policy.plugin_types
220
+ #
221
+ def plugin_types(*types)
222
+ if types.first
223
+ @directives["plugin-types"] = types
224
+ else
225
+ @directives.delete("plugin-types")
226
+ end
227
+ end
228
+
229
+ # Enable the [report-uri]
230
+ # (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-uri)
231
+ # directive. Violation reports will be sent to the
232
+ # specified URI:
233
+ #
234
+ # policy.report_uri "/csp-violation-report-endpoint"
235
+ #
236
+ def report_uri(uri)
237
+ @directives["report-uri"] = [uri]
238
+ end
239
+
240
+ # Specify asset types for which [Subresource Integrity]
241
+ # (https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) is required:
242
+ #
243
+ # policy.require_sri_for :script, :style
244
+ #
245
+ # Leave empty to not require Subresource Integrity:
246
+ #
247
+ # policy.require_sri_for
248
+ #
249
+ def require_sri_for(*types)
250
+ if types.first
251
+ @directives["require-sri-for"] = types
252
+ else
253
+ @directives.delete("require-sri-for")
254
+ end
255
+ end
256
+
257
+ # Specify whether a [sandbox]
258
+ # (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/sandbox)
259
+ # should be enabled for the requested resource:
260
+ #
261
+ # policy.sandbox
262
+ #
263
+ # Values can be passed as arguments:
264
+ #
265
+ # policy.sandbox "allow-scripts", "allow-modals"
266
+ #
267
+ # Pass `false` to disable the sandbox:
268
+ #
269
+ # policy.sandbox false
270
+ #
271
+ def sandbox(*values)
272
+ if values.empty?
273
+ @directives["sandbox"] = true
274
+ elsif values.first
275
+ @directives["sandbox"] = values
276
+ else
277
+ @directives.delete("sandbox")
278
+ end
279
+ end
280
+
281
+ # Specify whether user agents should treat any assets over HTTP as HTTPS:
282
+ #
283
+ # policy.upgrade_insecure_requests
284
+ #
285
+ # Pass `false` to disable it:
286
+ #
287
+ # policy.upgrade_insecure_requests false
288
+ #
289
+ def upgrade_insecure_requests(enabled = true)
290
+ if enabled
291
+ @directives["upgrade-insecure-requests"] = true
292
+ else
293
+ @directives.delete("upgrade-insecure-requests")
294
+ end
295
+ end
296
+
297
+ def build(context = nil, nonce = nil, nonce_directives = nil)
298
+ nonce_directives = DEFAULT_NONCE_DIRECTIVES if nonce_directives.nil?
299
+ build_directives(context, nonce, nonce_directives).compact.join("; ")
300
+ end
301
+
302
+ private
303
+ def apply_mappings(sources)
304
+ sources.map do |source|
305
+ case source
306
+ when Symbol
307
+ apply_mapping(source)
308
+ when String, Proc
309
+ source
310
+ else
311
+ raise ArgumentError, "Invalid content security policy source: #{source.inspect}"
312
+ end
313
+ end
314
+ end
315
+
316
+ def apply_mapping(source)
317
+ MAPPINGS.fetch(source) do
318
+ raise ArgumentError, "Unknown content security policy source mapping: #{source.inspect}"
319
+ end
320
+ end
321
+
322
+ def build_directives(context, nonce, nonce_directives)
323
+ @directives.map do |directive, sources|
324
+ if sources.is_a?(Array)
325
+ if nonce && nonce_directive?(directive, nonce_directives)
326
+ "#{directive} #{build_directive(sources, context).join(' ')} 'nonce-#{nonce}'"
327
+ else
328
+ "#{directive} #{build_directive(sources, context).join(' ')}"
329
+ end
330
+ elsif sources
331
+ directive
332
+ else
333
+ nil
334
+ end
335
+ end
336
+ end
337
+
338
+ def build_directive(sources, context)
339
+ sources.map { |source| resolve_source(source, context) }
340
+ end
341
+
342
+ def resolve_source(source, context)
343
+ case source
344
+ when String
345
+ source
346
+ when Symbol
347
+ source.to_s
348
+ when Proc
349
+ if context.nil?
350
+ raise RuntimeError, "Missing context for the dynamic content security policy source: #{source.inspect}"
351
+ else
352
+ resolved = context.instance_exec(&source)
353
+ apply_mappings(Array.wrap(resolved))
354
+ end
355
+ else
356
+ raise RuntimeError, "Unexpected content security policy source: #{source.inspect}"
357
+ end
358
+ end
359
+
360
+ def nonce_directive?(directive, nonce_directives)
361
+ nonce_directives.include?(directive)
362
+ end
363
+ end
364
+ end
@@ -1,76 +1,78 @@
1
- require 'active_support/core_ext/hash/keys'
2
- require 'active_support/core_ext/object/duplicable'
3
- require 'action_dispatch/http/parameter_filter'
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "active_support/parameter_filter"
4
6
 
5
7
  module ActionDispatch
6
8
  module Http
7
- # Allows you to specify sensitive parameters which will be replaced from
8
- # the request log by looking in the query string of the request and all
9
- # sub-hashes of the params hash to filter. If a block is given, each key and
10
- # value of the params hash and all sub-hashes is passed to it, the value
11
- # or key can be replaced using String#replace or similar method.
9
+ # # Action Dispatch HTTP Filter Parameters
12
10
  #
13
- # env["action_dispatch.parameter_filter"] = [:password]
14
- # => replaces the value to all keys matching /password/i with "[FILTERED]"
11
+ # Allows you to specify sensitive query string and POST parameters to filter
12
+ # from the request log.
15
13
  #
16
- # env["action_dispatch.parameter_filter"] = [:foo, "bar"]
17
- # => replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
14
+ # # Replaces values with "[FILTERED]" for keys that match /foo|bar/i.
15
+ # env["action_dispatch.parameter_filter"] = [:foo, "bar"]
18
16
  #
19
- # env["action_dispatch.parameter_filter"] = lambda do |k,v|
20
- # v.reverse! if k =~ /secret/i
21
- # end
22
- # => reverses the value to all keys matching /secret/i
17
+ # For more information about filter behavior, see
18
+ # ActiveSupport::ParameterFilter.
23
19
  module FilterParameters
24
20
  ENV_MATCH = [/RAW_POST_DATA/, "rack.request.form_vars"] # :nodoc:
25
- NULL_PARAM_FILTER = ParameterFilter.new # :nodoc:
26
- NULL_ENV_FILTER = ParameterFilter.new ENV_MATCH # :nodoc:
21
+ NULL_PARAM_FILTER = ActiveSupport::ParameterFilter.new # :nodoc:
22
+ NULL_ENV_FILTER = ActiveSupport::ParameterFilter.new ENV_MATCH # :nodoc:
27
23
 
28
- def initialize(env)
24
+ def initialize
29
25
  super
30
26
  @filtered_parameters = nil
31
27
  @filtered_env = nil
32
28
  @filtered_path = nil
29
+ @parameter_filter = nil
33
30
  end
34
31
 
35
- # Return a hash of parameters with all sensitive data replaced.
32
+ # Returns a hash of parameters with all sensitive data replaced.
36
33
  def filtered_parameters
37
34
  @filtered_parameters ||= parameter_filter.filter(parameters)
35
+ rescue ActionDispatch::Http::Parameters::ParseError
36
+ @filtered_parameters = {}
38
37
  end
39
38
 
40
- # Return a hash of request.env with all sensitive data replaced.
39
+ # Returns a hash of request.env with all sensitive data replaced.
41
40
  def filtered_env
42
41
  @filtered_env ||= env_filter.filter(@env)
43
42
  end
44
43
 
45
- # Reconstructed a path with all sensitive GET parameters replaced.
44
+ # Reconstructs a path with all sensitive GET parameters replaced.
46
45
  def filtered_path
47
46
  @filtered_path ||= query_string.empty? ? path : "#{path}?#{filtered_query_string}"
48
47
  end
49
48
 
50
- protected
51
-
49
+ # Returns the `ActiveSupport::ParameterFilter` object used to filter in this
50
+ # request.
52
51
  def parameter_filter
53
- parameter_filter_for @env.fetch("action_dispatch.parameter_filter") {
54
- return NULL_PARAM_FILTER
55
- }
52
+ @parameter_filter ||= if has_header?("action_dispatch.parameter_filter")
53
+ parameter_filter_for get_header("action_dispatch.parameter_filter")
54
+ else
55
+ NULL_PARAM_FILTER
56
+ end
56
57
  end
57
58
 
58
- def env_filter
59
- user_key = @env.fetch("action_dispatch.parameter_filter") {
59
+ private
60
+ def env_filter # :doc:
61
+ user_key = fetch_header("action_dispatch.parameter_filter") {
60
62
  return NULL_ENV_FILTER
61
63
  }
62
64
  parameter_filter_for(Array(user_key) + ENV_MATCH)
63
65
  end
64
66
 
65
- def parameter_filter_for(filters)
66
- ParameterFilter.new(filters)
67
+ def parameter_filter_for(filters) # :doc:
68
+ ActiveSupport::ParameterFilter.new(filters)
67
69
  end
68
70
 
69
- KV_RE = '[^&;=]+'
71
+ KV_RE = "[^&;=]+"
70
72
  PAIR_RE = %r{(#{KV_RE})=(#{KV_RE})}
71
- def filtered_query_string
73
+ def filtered_query_string # :doc:
72
74
  query_string.gsub(PAIR_RE) do |_|
73
- parameter_filter.filter([[$1, $2]]).first.join("=")
75
+ parameter_filter.filter($1 => $2).first.join("=")
74
76
  end
75
77
  end
76
78
  end
@@ -1,38 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
1
5
  module ActionDispatch
2
6
  module Http
3
7
  module FilterRedirect
8
+ FILTERED = "[FILTERED]" # :nodoc:
4
9
 
5
- FILTERED = '[FILTERED]'.freeze # :nodoc:
6
-
7
- def filtered_location
8
- filters = location_filter
9
- if !filters.empty? && location_filter_match?(filters)
10
+ def filtered_location # :nodoc:
11
+ if location_filter_match?
10
12
  FILTERED
11
13
  else
12
- location
14
+ parameter_filtered_location
13
15
  end
14
16
  end
15
17
 
16
18
  private
17
-
18
- def location_filter
19
+ def location_filters
19
20
  if request
20
- request.env['action_dispatch.redirect_filter'] || []
21
+ request.get_header("action_dispatch.redirect_filter") || []
21
22
  else
22
23
  []
23
24
  end
24
25
  end
25
26
 
26
- def location_filter_match?(filters)
27
- filters.any? do |filter|
27
+ def location_filter_match?
28
+ location_filters.any? do |filter|
28
29
  if String === filter
29
30
  location.include?(filter)
30
31
  elsif Regexp === filter
31
- location.match(filter)
32
+ location.match?(filter)
32
33
  end
33
34
  end
34
35
  end
35
36
 
37
+ def parameter_filtered_location
38
+ uri = URI.parse(location)
39
+ unless uri.query.nil? || uri.query.empty?
40
+ uri.query.gsub!(FilterParameters::PAIR_RE) do
41
+ request.parameter_filter.filter($1 => $2).first.join("=")
42
+ end
43
+ end
44
+ uri.to_s
45
+ rescue URI::Error
46
+ FILTERED
47
+ end
36
48
  end
37
49
  end
38
50
  end
@@ -1,10 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
1
5
  module ActionDispatch
2
6
  module Http
7
+ # # Action Dispatch HTTP Headers
8
+ #
3
9
  # Provides access to the request's HTTP headers from the environment.
4
10
  #
5
- # env = { "CONTENT_TYPE" => "text/plain" }
6
- # headers = ActionDispatch::Http::Headers.new(env)
7
- # headers["Content-Type"] # => "text/plain"
11
+ # env = { "CONTENT_TYPE" => "text/plain", "HTTP_USER_AGENT" => "curl/7.43.0" }
12
+ # headers = ActionDispatch::Http::Headers.from_hash(env)
13
+ # headers["Content-Type"] # => "text/plain"
14
+ # headers["User-Agent"] # => "curl/7.43.0"
15
+ #
16
+ # Also note that when headers are mapped to CGI-like variables by the Rack
17
+ # server, both dashes and underscores are converted to underscores. This
18
+ # ambiguity cannot be resolved at this stage anymore. Both underscores and
19
+ # dashes have to be interpreted as if they were originally sent as dashes.
20
+ #
21
+ # # GET / HTTP/1.1
22
+ # # ...
23
+ # # User-Agent: curl/7.43.0
24
+ # # X_Custom_Header: token
25
+ #
26
+ # headers["X_Custom_Header"] # => nil
27
+ # headers["X-Custom-Header"] # => "token"
8
28
  class Headers
9
29
  CGI_VARIABLES = Set.new(%W[
10
30
  AUTH_TYPE
@@ -30,70 +50,85 @@ module ActionDispatch
30
50
  HTTP_HEADER = /\A[A-Za-z0-9-]+\z/
31
51
 
32
52
  include Enumerable
33
- attr_reader :env
34
53
 
35
- def initialize(env = {}) # :nodoc:
36
- @env = env
54
+ def self.from_hash(hash)
55
+ new ActionDispatch::Request.new hash
56
+ end
57
+
58
+ def initialize(request) # :nodoc:
59
+ @req = request
37
60
  end
38
61
 
39
62
  # Returns the value for the given key mapped to @env.
40
63
  def [](key)
41
- @env[env_name(key)]
64
+ @req.get_header env_name(key)
42
65
  end
43
66
 
44
67
  # Sets the given value for the key mapped to @env.
45
68
  def []=(key, value)
46
- @env[env_name(key)] = value
69
+ @req.set_header env_name(key), value
70
+ end
71
+
72
+ # Add a value to a multivalued header like `Vary` or `Accept-Encoding`.
73
+ def add(key, value)
74
+ @req.add_header env_name(key), value
47
75
  end
48
76
 
49
77
  def key?(key)
50
- @env.key? env_name(key)
78
+ @req.has_header? env_name(key)
51
79
  end
52
80
  alias :include? :key?
53
81
 
82
+ DEFAULT = Object.new # :nodoc:
83
+
54
84
  # Returns the value for the given key mapped to @env.
55
85
  #
56
- # If the key is not found and an optional code block is not provided,
57
- # raises a <tt>KeyError</tt> exception.
86
+ # If the key is not found and an optional code block is not provided, raises a
87
+ # `KeyError` exception.
58
88
  #
59
- # If the code block is provided, then it will be run and
60
- # its result returned.
61
- def fetch(key, *args, &block)
62
- @env.fetch env_name(key), *args, &block
89
+ # If the code block is provided, then it will be run and its result returned.
90
+ def fetch(key, default = DEFAULT)
91
+ @req.fetch_header(env_name(key)) do
92
+ return default unless default == DEFAULT
93
+ return yield if block_given?
94
+ raise KeyError, key
95
+ end
63
96
  end
64
97
 
65
98
  def each(&block)
66
- @env.each(&block)
99
+ @req.each_header(&block)
67
100
  end
68
101
 
69
102
  # Returns a new Http::Headers instance containing the contents of
70
- # <tt>headers_or_env</tt> and the original instance.
103
+ # `headers_or_env` and the original instance.
71
104
  def merge(headers_or_env)
72
- headers = Http::Headers.new(env.dup)
105
+ headers = @req.dup.headers
73
106
  headers.merge!(headers_or_env)
74
107
  headers
75
108
  end
76
109
 
77
- # Adds the contents of <tt>headers_or_env</tt> to original instance
78
- # entries; duplicate keys are overwritten with the values from
79
- # <tt>headers_or_env</tt>.
110
+ # Adds the contents of `headers_or_env` to original instance entries; duplicate
111
+ # keys are overwritten with the values from `headers_or_env`.
80
112
  def merge!(headers_or_env)
81
113
  headers_or_env.each do |key, value|
82
- self[env_name(key)] = value
114
+ @req.set_header env_name(key), value
83
115
  end
84
116
  end
85
117
 
118
+ def env; @req.env.dup; end
119
+
86
120
  private
87
- # Converts a HTTP header name to an environment variable name if it is
88
- # not contained within the headers hash.
89
- def env_name(key)
90
- key = key.to_s
91
- if key =~ HTTP_HEADER
92
- key = key.upcase.tr('-', '_')
93
- key = "HTTP_" + key unless CGI_VARIABLES.include?(key)
121
+ # Converts an HTTP header name to an environment variable name if it is not
122
+ # contained within the headers hash.
123
+ def env_name(key)
124
+ key = key.to_s
125
+ if HTTP_HEADER.match?(key)
126
+ key = key.upcase
127
+ key.tr!("-", "_")
128
+ key.prepend("HTTP_") unless CGI_VARIABLES.include?(key)
129
+ end
130
+ key
94
131
  end
95
- key
96
- end
97
132
  end
98
133
  end
99
134
  end