omg-actionpack 8.0.0.alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +129 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +57 -0
  5. data/lib/abstract_controller/asset_paths.rb +14 -0
  6. data/lib/abstract_controller/base.rb +299 -0
  7. data/lib/abstract_controller/caching/fragments.rb +149 -0
  8. data/lib/abstract_controller/caching.rb +68 -0
  9. data/lib/abstract_controller/callbacks.rb +265 -0
  10. data/lib/abstract_controller/collector.rb +44 -0
  11. data/lib/abstract_controller/deprecator.rb +9 -0
  12. data/lib/abstract_controller/error.rb +8 -0
  13. data/lib/abstract_controller/helpers.rb +243 -0
  14. data/lib/abstract_controller/logger.rb +16 -0
  15. data/lib/abstract_controller/railties/routes_helpers.rb +25 -0
  16. data/lib/abstract_controller/rendering.rb +126 -0
  17. data/lib/abstract_controller/translation.rb +42 -0
  18. data/lib/abstract_controller/url_for.rb +37 -0
  19. data/lib/abstract_controller.rb +36 -0
  20. data/lib/action_controller/api/api_rendering.rb +18 -0
  21. data/lib/action_controller/api.rb +155 -0
  22. data/lib/action_controller/base.rb +332 -0
  23. data/lib/action_controller/caching.rb +49 -0
  24. data/lib/action_controller/deprecator.rb +9 -0
  25. data/lib/action_controller/form_builder.rb +55 -0
  26. data/lib/action_controller/log_subscriber.rb +96 -0
  27. data/lib/action_controller/metal/allow_browser.rb +123 -0
  28. data/lib/action_controller/metal/basic_implicit_render.rb +17 -0
  29. data/lib/action_controller/metal/conditional_get.rb +341 -0
  30. data/lib/action_controller/metal/content_security_policy.rb +86 -0
  31. data/lib/action_controller/metal/cookies.rb +20 -0
  32. data/lib/action_controller/metal/data_streaming.rb +154 -0
  33. data/lib/action_controller/metal/default_headers.rb +21 -0
  34. data/lib/action_controller/metal/etag_with_flash.rb +22 -0
  35. data/lib/action_controller/metal/etag_with_template_digest.rb +59 -0
  36. data/lib/action_controller/metal/exceptions.rb +106 -0
  37. data/lib/action_controller/metal/flash.rb +67 -0
  38. data/lib/action_controller/metal/head.rb +67 -0
  39. data/lib/action_controller/metal/helpers.rb +129 -0
  40. data/lib/action_controller/metal/http_authentication.rb +565 -0
  41. data/lib/action_controller/metal/implicit_render.rb +67 -0
  42. data/lib/action_controller/metal/instrumentation.rb +120 -0
  43. data/lib/action_controller/metal/live.rb +398 -0
  44. data/lib/action_controller/metal/logging.rb +22 -0
  45. data/lib/action_controller/metal/mime_responds.rb +337 -0
  46. data/lib/action_controller/metal/parameter_encoding.rb +84 -0
  47. data/lib/action_controller/metal/params_wrapper.rb +312 -0
  48. data/lib/action_controller/metal/permissions_policy.rb +38 -0
  49. data/lib/action_controller/metal/rate_limiting.rb +62 -0
  50. data/lib/action_controller/metal/redirecting.rb +251 -0
  51. data/lib/action_controller/metal/renderers.rb +181 -0
  52. data/lib/action_controller/metal/rendering.rb +260 -0
  53. data/lib/action_controller/metal/request_forgery_protection.rb +667 -0
  54. data/lib/action_controller/metal/rescue.rb +33 -0
  55. data/lib/action_controller/metal/streaming.rb +183 -0
  56. data/lib/action_controller/metal/strong_parameters.rb +1546 -0
  57. data/lib/action_controller/metal/testing.rb +25 -0
  58. data/lib/action_controller/metal/url_for.rb +65 -0
  59. data/lib/action_controller/metal.rb +339 -0
  60. data/lib/action_controller/railtie.rb +149 -0
  61. data/lib/action_controller/railties/helpers.rb +26 -0
  62. data/lib/action_controller/renderer.rb +161 -0
  63. data/lib/action_controller/template_assertions.rb +13 -0
  64. data/lib/action_controller/test_case.rb +691 -0
  65. data/lib/action_controller.rb +80 -0
  66. data/lib/action_dispatch/constants.rb +34 -0
  67. data/lib/action_dispatch/deprecator.rb +9 -0
  68. data/lib/action_dispatch/http/cache.rb +249 -0
  69. data/lib/action_dispatch/http/content_disposition.rb +47 -0
  70. data/lib/action_dispatch/http/content_security_policy.rb +365 -0
  71. data/lib/action_dispatch/http/filter_parameters.rb +80 -0
  72. data/lib/action_dispatch/http/filter_redirect.rb +50 -0
  73. data/lib/action_dispatch/http/headers.rb +134 -0
  74. data/lib/action_dispatch/http/mime_negotiation.rb +187 -0
  75. data/lib/action_dispatch/http/mime_type.rb +389 -0
  76. data/lib/action_dispatch/http/mime_types.rb +54 -0
  77. data/lib/action_dispatch/http/parameters.rb +119 -0
  78. data/lib/action_dispatch/http/permissions_policy.rb +189 -0
  79. data/lib/action_dispatch/http/rack_cache.rb +67 -0
  80. data/lib/action_dispatch/http/request.rb +498 -0
  81. data/lib/action_dispatch/http/response.rb +556 -0
  82. data/lib/action_dispatch/http/upload.rb +107 -0
  83. data/lib/action_dispatch/http/url.rb +344 -0
  84. data/lib/action_dispatch/journey/formatter.rb +226 -0
  85. data/lib/action_dispatch/journey/gtg/builder.rb +149 -0
  86. data/lib/action_dispatch/journey/gtg/simulator.rb +50 -0
  87. data/lib/action_dispatch/journey/gtg/transition_table.rb +217 -0
  88. data/lib/action_dispatch/journey/nfa/dot.rb +27 -0
  89. data/lib/action_dispatch/journey/nodes/node.rb +208 -0
  90. data/lib/action_dispatch/journey/parser.rb +103 -0
  91. data/lib/action_dispatch/journey/path/pattern.rb +209 -0
  92. data/lib/action_dispatch/journey/route.rb +189 -0
  93. data/lib/action_dispatch/journey/router/utils.rb +105 -0
  94. data/lib/action_dispatch/journey/router.rb +151 -0
  95. data/lib/action_dispatch/journey/routes.rb +82 -0
  96. data/lib/action_dispatch/journey/scanner.rb +70 -0
  97. data/lib/action_dispatch/journey/visitors.rb +267 -0
  98. data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
  99. data/lib/action_dispatch/journey/visualizer/fsm.js +159 -0
  100. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  101. data/lib/action_dispatch/journey.rb +7 -0
  102. data/lib/action_dispatch/log_subscriber.rb +25 -0
  103. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  104. data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
  105. data/lib/action_dispatch/middleware/callbacks.rb +38 -0
  106. data/lib/action_dispatch/middleware/cookies.rb +719 -0
  107. data/lib/action_dispatch/middleware/debug_exceptions.rb +206 -0
  108. data/lib/action_dispatch/middleware/debug_locks.rb +129 -0
  109. data/lib/action_dispatch/middleware/debug_view.rb +73 -0
  110. data/lib/action_dispatch/middleware/exception_wrapper.rb +350 -0
  111. data/lib/action_dispatch/middleware/executor.rb +32 -0
  112. data/lib/action_dispatch/middleware/flash.rb +318 -0
  113. data/lib/action_dispatch/middleware/host_authorization.rb +171 -0
  114. data/lib/action_dispatch/middleware/public_exceptions.rb +64 -0
  115. data/lib/action_dispatch/middleware/reloader.rb +16 -0
  116. data/lib/action_dispatch/middleware/remote_ip.rb +199 -0
  117. data/lib/action_dispatch/middleware/request_id.rb +50 -0
  118. data/lib/action_dispatch/middleware/server_timing.rb +78 -0
  119. data/lib/action_dispatch/middleware/session/abstract_store.rb +112 -0
  120. data/lib/action_dispatch/middleware/session/cache_store.rb +66 -0
  121. data/lib/action_dispatch/middleware/session/cookie_store.rb +129 -0
  122. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +34 -0
  123. data/lib/action_dispatch/middleware/show_exceptions.rb +88 -0
  124. data/lib/action_dispatch/middleware/ssl.rb +180 -0
  125. data/lib/action_dispatch/middleware/stack.rb +194 -0
  126. data/lib/action_dispatch/middleware/static.rb +192 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  128. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +17 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
  132. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +36 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +62 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
  136. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +12 -0
  137. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +9 -0
  138. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +35 -0
  139. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  140. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
  141. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +16 -0
  142. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +284 -0
  143. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +23 -0
  144. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  145. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
  146. data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
  147. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
  148. data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
  149. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
  150. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
  151. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
  152. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
  153. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +19 -0
  154. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +232 -0
  155. data/lib/action_dispatch/railtie.rb +77 -0
  156. data/lib/action_dispatch/request/session.rb +283 -0
  157. data/lib/action_dispatch/request/utils.rb +109 -0
  158. data/lib/action_dispatch/routing/endpoint.rb +19 -0
  159. data/lib/action_dispatch/routing/inspector.rb +323 -0
  160. data/lib/action_dispatch/routing/mapper.rb +2372 -0
  161. data/lib/action_dispatch/routing/polymorphic_routes.rb +363 -0
  162. data/lib/action_dispatch/routing/redirection.rb +218 -0
  163. data/lib/action_dispatch/routing/route_set.rb +958 -0
  164. data/lib/action_dispatch/routing/routes_proxy.rb +66 -0
  165. data/lib/action_dispatch/routing/url_for.rb +244 -0
  166. data/lib/action_dispatch/routing.rb +262 -0
  167. data/lib/action_dispatch/system_test_case.rb +206 -0
  168. data/lib/action_dispatch/system_testing/browser.rb +75 -0
  169. data/lib/action_dispatch/system_testing/driver.rb +85 -0
  170. data/lib/action_dispatch/system_testing/server.rb +33 -0
  171. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +164 -0
  172. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +23 -0
  173. data/lib/action_dispatch/testing/assertion_response.rb +48 -0
  174. data/lib/action_dispatch/testing/assertions/response.rb +114 -0
  175. data/lib/action_dispatch/testing/assertions/routing.rb +343 -0
  176. data/lib/action_dispatch/testing/assertions.rb +25 -0
  177. data/lib/action_dispatch/testing/integration.rb +694 -0
  178. data/lib/action_dispatch/testing/request_encoder.rb +60 -0
  179. data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
  180. data/lib/action_dispatch/testing/test_process.rb +57 -0
  181. data/lib/action_dispatch/testing/test_request.rb +73 -0
  182. data/lib/action_dispatch/testing/test_response.rb +58 -0
  183. data/lib/action_dispatch.rb +147 -0
  184. data/lib/action_pack/gem_version.rb +19 -0
  185. data/lib/action_pack/version.rb +12 -0
  186. data/lib/action_pack.rb +27 -0
  187. metadata +375 -0
@@ -0,0 +1,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