omg-actionpack 8.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
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,365 @@
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
+ wasm_unsafe_eval: "'wasm-unsafe-eval'",
130
+ unsafe_hashes: "'unsafe-hashes'",
131
+ unsafe_inline: "'unsafe-inline'",
132
+ none: "'none'",
133
+ http: "http:",
134
+ https: "https:",
135
+ data: "data:",
136
+ mediastream: "mediastream:",
137
+ allow_duplicates: "'allow-duplicates'",
138
+ blob: "blob:",
139
+ filesystem: "filesystem:",
140
+ report_sample: "'report-sample'",
141
+ script: "'script'",
142
+ strict_dynamic: "'strict-dynamic'",
143
+ ws: "ws:",
144
+ wss: "wss:"
145
+ }.freeze
146
+
147
+ DIRECTIVES = {
148
+ base_uri: "base-uri",
149
+ child_src: "child-src",
150
+ connect_src: "connect-src",
151
+ default_src: "default-src",
152
+ font_src: "font-src",
153
+ form_action: "form-action",
154
+ frame_ancestors: "frame-ancestors",
155
+ frame_src: "frame-src",
156
+ img_src: "img-src",
157
+ manifest_src: "manifest-src",
158
+ media_src: "media-src",
159
+ object_src: "object-src",
160
+ prefetch_src: "prefetch-src",
161
+ require_trusted_types_for: "require-trusted-types-for",
162
+ script_src: "script-src",
163
+ script_src_attr: "script-src-attr",
164
+ script_src_elem: "script-src-elem",
165
+ style_src: "style-src",
166
+ style_src_attr: "style-src-attr",
167
+ style_src_elem: "style-src-elem",
168
+ trusted_types: "trusted-types",
169
+ worker_src: "worker-src"
170
+ }.freeze
171
+
172
+ DEFAULT_NONCE_DIRECTIVES = %w[script-src style-src].freeze
173
+
174
+ private_constant :MAPPINGS, :DIRECTIVES, :DEFAULT_NONCE_DIRECTIVES
175
+
176
+ attr_reader :directives
177
+
178
+ def initialize
179
+ @directives = {}
180
+ yield self if block_given?
181
+ end
182
+
183
+ def initialize_copy(other)
184
+ @directives = other.directives.deep_dup
185
+ end
186
+
187
+ DIRECTIVES.each do |name, directive|
188
+ define_method(name) do |*sources|
189
+ if sources.first
190
+ @directives[directive] = apply_mappings(sources)
191
+ else
192
+ @directives.delete(directive)
193
+ end
194
+ end
195
+ end
196
+
197
+ # Specify whether to prevent the user agent from loading any assets over HTTP
198
+ # when the page uses HTTPS:
199
+ #
200
+ # policy.block_all_mixed_content
201
+ #
202
+ # Pass `false` to allow it again:
203
+ #
204
+ # policy.block_all_mixed_content false
205
+ #
206
+ def block_all_mixed_content(enabled = true)
207
+ if enabled
208
+ @directives["block-all-mixed-content"] = true
209
+ else
210
+ @directives.delete("block-all-mixed-content")
211
+ end
212
+ end
213
+
214
+ # Restricts the set of plugins that can be embedded:
215
+ #
216
+ # policy.plugin_types "application/x-shockwave-flash"
217
+ #
218
+ # Leave empty to allow all plugins:
219
+ #
220
+ # policy.plugin_types
221
+ #
222
+ def plugin_types(*types)
223
+ if types.first
224
+ @directives["plugin-types"] = types
225
+ else
226
+ @directives.delete("plugin-types")
227
+ end
228
+ end
229
+
230
+ # Enable the [report-uri]
231
+ # (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-uri)
232
+ # directive. Violation reports will be sent to the
233
+ # specified URI:
234
+ #
235
+ # policy.report_uri "/csp-violation-report-endpoint"
236
+ #
237
+ def report_uri(uri)
238
+ @directives["report-uri"] = [uri]
239
+ end
240
+
241
+ # Specify asset types for which [Subresource Integrity]
242
+ # (https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) is required:
243
+ #
244
+ # policy.require_sri_for :script, :style
245
+ #
246
+ # Leave empty to not require Subresource Integrity:
247
+ #
248
+ # policy.require_sri_for
249
+ #
250
+ def require_sri_for(*types)
251
+ if types.first
252
+ @directives["require-sri-for"] = types
253
+ else
254
+ @directives.delete("require-sri-for")
255
+ end
256
+ end
257
+
258
+ # Specify whether a [sandbox]
259
+ # (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/sandbox)
260
+ # should be enabled for the requested resource:
261
+ #
262
+ # policy.sandbox
263
+ #
264
+ # Values can be passed as arguments:
265
+ #
266
+ # policy.sandbox "allow-scripts", "allow-modals"
267
+ #
268
+ # Pass `false` to disable the sandbox:
269
+ #
270
+ # policy.sandbox false
271
+ #
272
+ def sandbox(*values)
273
+ if values.empty?
274
+ @directives["sandbox"] = true
275
+ elsif values.first
276
+ @directives["sandbox"] = values
277
+ else
278
+ @directives.delete("sandbox")
279
+ end
280
+ end
281
+
282
+ # Specify whether user agents should treat any assets over HTTP as HTTPS:
283
+ #
284
+ # policy.upgrade_insecure_requests
285
+ #
286
+ # Pass `false` to disable it:
287
+ #
288
+ # policy.upgrade_insecure_requests false
289
+ #
290
+ def upgrade_insecure_requests(enabled = true)
291
+ if enabled
292
+ @directives["upgrade-insecure-requests"] = true
293
+ else
294
+ @directives.delete("upgrade-insecure-requests")
295
+ end
296
+ end
297
+
298
+ def build(context = nil, nonce = nil, nonce_directives = nil)
299
+ nonce_directives = DEFAULT_NONCE_DIRECTIVES if nonce_directives.nil?
300
+ build_directives(context, nonce, nonce_directives).compact.join("; ")
301
+ end
302
+
303
+ private
304
+ def apply_mappings(sources)
305
+ sources.map do |source|
306
+ case source
307
+ when Symbol
308
+ apply_mapping(source)
309
+ when String, Proc
310
+ source
311
+ else
312
+ raise ArgumentError, "Invalid content security policy source: #{source.inspect}"
313
+ end
314
+ end
315
+ end
316
+
317
+ def apply_mapping(source)
318
+ MAPPINGS.fetch(source) do
319
+ raise ArgumentError, "Unknown content security policy source mapping: #{source.inspect}"
320
+ end
321
+ end
322
+
323
+ def build_directives(context, nonce, nonce_directives)
324
+ @directives.map do |directive, sources|
325
+ if sources.is_a?(Array)
326
+ if nonce && nonce_directive?(directive, nonce_directives)
327
+ "#{directive} #{build_directive(sources, context).join(' ')} 'nonce-#{nonce}'"
328
+ else
329
+ "#{directive} #{build_directive(sources, context).join(' ')}"
330
+ end
331
+ elsif sources
332
+ directive
333
+ else
334
+ nil
335
+ end
336
+ end
337
+ end
338
+
339
+ def build_directive(sources, context)
340
+ sources.map { |source| resolve_source(source, context) }
341
+ end
342
+
343
+ def resolve_source(source, context)
344
+ case source
345
+ when String
346
+ source
347
+ when Symbol
348
+ source.to_s
349
+ when Proc
350
+ if context.nil?
351
+ raise RuntimeError, "Missing context for the dynamic content security policy source: #{source.inspect}"
352
+ else
353
+ resolved = context.instance_exec(&source)
354
+ apply_mappings(Array.wrap(resolved))
355
+ end
356
+ else
357
+ raise RuntimeError, "Unexpected content security policy source: #{source.inspect}"
358
+ end
359
+ end
360
+
361
+ def nonce_directive?(directive, nonce_directives)
362
+ nonce_directives.include?(directive)
363
+ end
364
+ end
365
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "active_support/parameter_filter"
6
+
7
+ module ActionDispatch
8
+ module Http
9
+ # # Action Dispatch HTTP Filter Parameters
10
+ #
11
+ # Allows you to specify sensitive query string and POST parameters to filter
12
+ # from the request log.
13
+ #
14
+ # # Replaces values with "[FILTERED]" for keys that match /foo|bar/i.
15
+ # env["action_dispatch.parameter_filter"] = [:foo, "bar"]
16
+ #
17
+ # For more information about filter behavior, see
18
+ # ActiveSupport::ParameterFilter.
19
+ module FilterParameters
20
+ ENV_MATCH = [/RAW_POST_DATA/, "rack.request.form_vars"] # :nodoc:
21
+ NULL_PARAM_FILTER = ActiveSupport::ParameterFilter.new # :nodoc:
22
+ NULL_ENV_FILTER = ActiveSupport::ParameterFilter.new ENV_MATCH # :nodoc:
23
+
24
+ def initialize
25
+ super
26
+ @filtered_parameters = nil
27
+ @filtered_env = nil
28
+ @filtered_path = nil
29
+ @parameter_filter = nil
30
+ end
31
+
32
+ # Returns a hash of parameters with all sensitive data replaced.
33
+ def filtered_parameters
34
+ @filtered_parameters ||= parameter_filter.filter(parameters)
35
+ rescue ActionDispatch::Http::Parameters::ParseError
36
+ @filtered_parameters = {}
37
+ end
38
+
39
+ # Returns a hash of request.env with all sensitive data replaced.
40
+ def filtered_env
41
+ @filtered_env ||= env_filter.filter(@env)
42
+ end
43
+
44
+ # Reconstructs a path with all sensitive GET parameters replaced.
45
+ def filtered_path
46
+ @filtered_path ||= query_string.empty? ? path : "#{path}?#{filtered_query_string}"
47
+ end
48
+
49
+ # Returns the `ActiveSupport::ParameterFilter` object used to filter in this
50
+ # request.
51
+ def parameter_filter
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
57
+ end
58
+
59
+ private
60
+ def env_filter # :doc:
61
+ user_key = fetch_header("action_dispatch.parameter_filter") {
62
+ return NULL_ENV_FILTER
63
+ }
64
+ parameter_filter_for(Array(user_key) + ENV_MATCH)
65
+ end
66
+
67
+ def parameter_filter_for(filters) # :doc:
68
+ ActiveSupport::ParameterFilter.new(filters)
69
+ end
70
+
71
+ KV_RE = "[^&;=]+"
72
+ PAIR_RE = %r{(#{KV_RE})=(#{KV_RE})}
73
+ def filtered_query_string # :doc:
74
+ query_string.gsub(PAIR_RE) do |_|
75
+ parameter_filter.filter($1 => $2).first.join("=")
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ module ActionDispatch
6
+ module Http
7
+ module FilterRedirect
8
+ FILTERED = "[FILTERED]" # :nodoc:
9
+
10
+ def filtered_location # :nodoc:
11
+ if location_filter_match?
12
+ FILTERED
13
+ else
14
+ parameter_filtered_location
15
+ end
16
+ end
17
+
18
+ private
19
+ def location_filters
20
+ if request
21
+ request.get_header("action_dispatch.redirect_filter") || []
22
+ else
23
+ []
24
+ end
25
+ end
26
+
27
+ def location_filter_match?
28
+ location_filters.any? do |filter|
29
+ if String === filter
30
+ location.include?(filter)
31
+ elsif Regexp === filter
32
+ location.match?(filter)
33
+ end
34
+ end
35
+ end
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
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ module ActionDispatch
6
+ module Http
7
+ # # Action Dispatch HTTP Headers
8
+ #
9
+ # Provides access to the request's HTTP headers from the environment.
10
+ #
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"
28
+ class Headers
29
+ CGI_VARIABLES = Set.new(%W[
30
+ AUTH_TYPE
31
+ CONTENT_LENGTH
32
+ CONTENT_TYPE
33
+ GATEWAY_INTERFACE
34
+ HTTPS
35
+ PATH_INFO
36
+ PATH_TRANSLATED
37
+ QUERY_STRING
38
+ REMOTE_ADDR
39
+ REMOTE_HOST
40
+ REMOTE_IDENT
41
+ REMOTE_USER
42
+ REQUEST_METHOD
43
+ SCRIPT_NAME
44
+ SERVER_NAME
45
+ SERVER_PORT
46
+ SERVER_PROTOCOL
47
+ SERVER_SOFTWARE
48
+ ]).freeze
49
+
50
+ HTTP_HEADER = /\A[A-Za-z0-9-]+\z/
51
+
52
+ include Enumerable
53
+
54
+ def self.from_hash(hash)
55
+ new ActionDispatch::Request.new hash
56
+ end
57
+
58
+ def initialize(request) # :nodoc:
59
+ @req = request
60
+ end
61
+
62
+ # Returns the value for the given key mapped to @env.
63
+ def [](key)
64
+ @req.get_header env_name(key)
65
+ end
66
+
67
+ # Sets the given value for the key mapped to @env.
68
+ def []=(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
75
+ end
76
+
77
+ def key?(key)
78
+ @req.has_header? env_name(key)
79
+ end
80
+ alias :include? :key?
81
+
82
+ DEFAULT = Object.new # :nodoc:
83
+
84
+ # Returns the value for the given key mapped to @env.
85
+ #
86
+ # If the key is not found and an optional code block is not provided, raises a
87
+ # `KeyError` exception.
88
+ #
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
96
+ end
97
+
98
+ def each(&block)
99
+ @req.each_header(&block)
100
+ end
101
+
102
+ # Returns a new Http::Headers instance containing the contents of
103
+ # `headers_or_env` and the original instance.
104
+ def merge(headers_or_env)
105
+ headers = @req.dup.headers
106
+ headers.merge!(headers_or_env)
107
+ headers
108
+ end
109
+
110
+ # Adds the contents of `headers_or_env` to original instance entries; duplicate
111
+ # keys are overwritten with the values from `headers_or_env`.
112
+ def merge!(headers_or_env)
113
+ headers_or_env.each do |key, value|
114
+ @req.set_header env_name(key), value
115
+ end
116
+ end
117
+
118
+ def env; @req.env.dup; end
119
+
120
+ private
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
131
+ end
132
+ end
133
+ end
134
+ end