actionpack 5.1.7 → 5.2.4.3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +282 -362
- data/MIT-LICENSE +1 -1
- data/README.rdoc +5 -5
- data/lib/abstract_controller.rb +3 -0
- data/lib/abstract_controller/asset_paths.rb +2 -0
- data/lib/abstract_controller/base.rb +10 -2
- data/lib/abstract_controller/caching.rb +3 -2
- data/lib/abstract_controller/caching/fragments.rb +30 -7
- data/lib/abstract_controller/callbacks.rb +25 -3
- data/lib/abstract_controller/collector.rb +2 -0
- data/lib/abstract_controller/error.rb +2 -0
- data/lib/abstract_controller/helpers.rb +4 -5
- data/lib/abstract_controller/logger.rb +2 -0
- data/lib/abstract_controller/railties/routes_helpers.rb +2 -0
- data/lib/abstract_controller/rendering.rb +9 -16
- data/lib/abstract_controller/translation.rb +2 -0
- data/lib/abstract_controller/url_for.rb +2 -0
- data/lib/action_controller.rb +3 -0
- data/lib/action_controller/api.rb +2 -0
- data/lib/action_controller/api/api_rendering.rb +2 -0
- data/lib/action_controller/base.rb +3 -0
- data/lib/action_controller/caching.rb +2 -0
- data/lib/action_controller/form_builder.rb +2 -0
- data/lib/action_controller/log_subscriber.rb +5 -3
- data/lib/action_controller/metal.rb +13 -14
- data/lib/action_controller/metal/basic_implicit_render.rb +2 -0
- data/lib/action_controller/metal/conditional_get.rb +4 -3
- data/lib/action_controller/metal/content_security_policy.rb +52 -0
- data/lib/action_controller/metal/cookies.rb +2 -0
- data/lib/action_controller/metal/data_streaming.rb +7 -5
- data/lib/action_controller/metal/etag_with_flash.rb +2 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +3 -2
- data/lib/action_controller/metal/exceptions.rb +2 -3
- data/lib/action_controller/metal/flash.rb +3 -2
- data/lib/action_controller/metal/force_ssl.rb +4 -2
- data/lib/action_controller/metal/head.rb +2 -0
- data/lib/action_controller/metal/helpers.rb +4 -3
- data/lib/action_controller/metal/http_authentication.rb +8 -9
- data/lib/action_controller/metal/implicit_render.rb +2 -0
- data/lib/action_controller/metal/instrumentation.rb +4 -6
- data/lib/action_controller/metal/live.rb +3 -1
- data/lib/action_controller/metal/mime_responds.rb +3 -1
- data/lib/action_controller/metal/parameter_encoding.rb +2 -0
- data/lib/action_controller/metal/params_wrapper.rb +14 -10
- data/lib/action_controller/metal/redirecting.rb +22 -11
- data/lib/action_controller/metal/renderers.rb +4 -3
- data/lib/action_controller/metal/rendering.rb +2 -2
- data/lib/action_controller/metal/request_forgery_protection.rb +62 -10
- data/lib/action_controller/metal/rescue.rb +5 -3
- data/lib/action_controller/metal/streaming.rb +3 -1
- data/lib/action_controller/metal/strong_parameters.rb +36 -25
- data/lib/action_controller/metal/testing.rb +2 -6
- data/lib/action_controller/metal/url_for.rb +2 -0
- data/lib/action_controller/railtie.rb +16 -4
- data/lib/action_controller/railties/helpers.rb +2 -0
- data/lib/action_controller/renderer.rb +2 -0
- data/lib/action_controller/template_assertions.rb +2 -0
- data/lib/action_controller/test_case.rb +16 -10
- data/lib/action_dispatch.rb +9 -5
- data/lib/action_dispatch/http/cache.rb +22 -14
- data/lib/action_dispatch/http/content_security_policy.rb +272 -0
- data/lib/action_dispatch/http/filter_parameters.rb +4 -2
- data/lib/action_dispatch/http/filter_redirect.rb +2 -0
- data/lib/action_dispatch/http/headers.rb +2 -0
- data/lib/action_dispatch/http/mime_negotiation.rb +4 -8
- data/lib/action_dispatch/http/mime_type.rb +15 -13
- data/lib/action_dispatch/http/mime_types.rb +17 -2
- data/lib/action_dispatch/http/parameter_filter.rb +2 -0
- data/lib/action_dispatch/http/parameters.rb +6 -9
- data/lib/action_dispatch/http/rack_cache.rb +2 -0
- data/lib/action_dispatch/http/request.rb +36 -16
- data/lib/action_dispatch/http/response.rb +11 -9
- data/lib/action_dispatch/http/upload.rb +2 -0
- data/lib/action_dispatch/http/url.rb +5 -6
- data/lib/action_dispatch/journey.rb +2 -0
- data/lib/action_dispatch/journey/formatter.rb +4 -2
- data/lib/action_dispatch/journey/gtg/builder.rb +2 -0
- data/lib/action_dispatch/journey/gtg/simulator.rb +2 -8
- data/lib/action_dispatch/journey/gtg/transition_table.rb +3 -2
- data/lib/action_dispatch/journey/nfa/builder.rb +2 -0
- data/lib/action_dispatch/journey/nfa/dot.rb +12 -10
- data/lib/action_dispatch/journey/nfa/simulator.rb +2 -0
- data/lib/action_dispatch/journey/nfa/transition_table.rb +2 -0
- data/lib/action_dispatch/journey/nodes/node.rb +2 -0
- data/lib/action_dispatch/journey/parser_extras.rb +2 -0
- data/lib/action_dispatch/journey/path/pattern.rb +4 -1
- data/lib/action_dispatch/journey/route.rb +15 -6
- data/lib/action_dispatch/journey/router.rb +3 -1
- data/lib/action_dispatch/journey/router/utils.rb +14 -7
- data/lib/action_dispatch/journey/routes.rb +3 -1
- data/lib/action_dispatch/journey/scanner.rb +1 -0
- data/lib/action_dispatch/journey/visitors.rb +5 -3
- data/lib/action_dispatch/middleware/callbacks.rb +2 -0
- data/lib/action_dispatch/middleware/cookies.rb +148 -91
- data/lib/action_dispatch/middleware/debug_exceptions.rb +4 -2
- data/lib/action_dispatch/middleware/debug_locks.rb +9 -7
- data/lib/action_dispatch/middleware/exception_wrapper.rb +5 -6
- data/lib/action_dispatch/middleware/executor.rb +2 -0
- data/lib/action_dispatch/middleware/flash.rb +4 -2
- data/lib/action_dispatch/middleware/public_exceptions.rb +6 -4
- data/lib/action_dispatch/middleware/reloader.rb +2 -0
- data/lib/action_dispatch/middleware/remote_ip.rb +7 -5
- data/lib/action_dispatch/middleware/request_id.rb +3 -1
- data/lib/action_dispatch/middleware/session/abstract_store.rb +17 -1
- data/lib/action_dispatch/middleware/session/cache_store.rb +13 -6
- data/lib/action_dispatch/middleware/session/cookie_store.rb +31 -32
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +2 -0
- data/lib/action_dispatch/middleware/show_exceptions.rb +3 -1
- data/lib/action_dispatch/middleware/ssl.rb +44 -38
- data/lib/action_dispatch/middleware/stack.rb +4 -2
- data/lib/action_dispatch/middleware/static.rb +14 -12
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +21 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +13 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +1 -0
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +6 -2
- data/lib/action_dispatch/railtie.rb +11 -1
- data/lib/action_dispatch/request/session.rb +16 -5
- data/lib/action_dispatch/request/utils.rb +6 -4
- data/lib/action_dispatch/routing.rb +3 -1
- data/lib/action_dispatch/routing/endpoint.rb +9 -2
- data/lib/action_dispatch/routing/inspector.rb +6 -4
- data/lib/action_dispatch/routing/mapper.rb +64 -52
- data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -0
- data/lib/action_dispatch/routing/redirection.rb +7 -5
- data/lib/action_dispatch/routing/route_set.rb +29 -24
- data/lib/action_dispatch/routing/routes_proxy.rb +5 -2
- data/lib/action_dispatch/routing/url_for.rb +25 -5
- data/lib/action_dispatch/system_test_case.rb +22 -6
- data/lib/action_dispatch/system_testing/browser.rb +49 -0
- data/lib/action_dispatch/system_testing/driver.rb +9 -3
- data/lib/action_dispatch/system_testing/server.rb +2 -16
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +12 -14
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +8 -2
- data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +26 -0
- data/lib/action_dispatch/testing/assertion_response.rb +2 -0
- data/lib/action_dispatch/testing/assertions.rb +2 -0
- data/lib/action_dispatch/testing/assertions/response.rb +4 -2
- data/lib/action_dispatch/testing/assertions/routing.rb +5 -5
- data/lib/action_dispatch/testing/integration.rb +24 -21
- data/lib/action_dispatch/testing/request_encoder.rb +3 -1
- data/lib/action_dispatch/testing/test_process.rb +2 -0
- data/lib/action_dispatch/testing/test_request.rb +3 -1
- data/lib/action_dispatch/testing/test_response.rb +23 -3
- data/lib/action_pack.rb +3 -1
- data/lib/action_pack/gem_version.rb +5 -3
- data/lib/action_pack/version.rb +2 -0
- metadata +23 -11
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActionController
|
2
4
|
module Testing
|
3
5
|
extend ActiveSupport::Concern
|
@@ -10,11 +12,5 @@ module ActionController
|
|
10
12
|
self.params = nil
|
11
13
|
end
|
12
14
|
end
|
13
|
-
|
14
|
-
module ClassMethods
|
15
|
-
def before_filters
|
16
|
-
_process_action_callbacks.find_all { |x| x.kind == :before }.map(&:name)
|
17
|
-
end
|
18
|
-
end
|
19
15
|
end
|
20
16
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "rails"
|
2
4
|
require "action_controller"
|
3
5
|
require "action_dispatch/railtie"
|
@@ -23,10 +25,6 @@ module ActionController
|
|
23
25
|
options = app.config.action_controller
|
24
26
|
|
25
27
|
ActiveSupport.on_load(:action_controller, run_once: true) do
|
26
|
-
if options.delete(:raise_on_unfiltered_parameters)
|
27
|
-
ActiveSupport::Deprecation.warn("raise_on_unfiltered_parameters is deprecated and has no effect in Rails 5.1.")
|
28
|
-
end
|
29
|
-
|
30
28
|
ActionController::Parameters.permit_all_parameters = options.delete(:permit_all_parameters) { false }
|
31
29
|
if app.config.action_controller[:always_permitted_parameters]
|
32
30
|
ActionController::Parameters.always_permitted_parameters =
|
@@ -73,5 +71,19 @@ module ActionController
|
|
73
71
|
config.compile_methods! if config.respond_to?(:compile_methods!)
|
74
72
|
end
|
75
73
|
end
|
74
|
+
|
75
|
+
initializer "action_controller.request_forgery_protection" do |app|
|
76
|
+
ActiveSupport.on_load(:action_controller_base) do
|
77
|
+
if app.config.action_controller.default_protect_from_forgery
|
78
|
+
protect_from_forgery with: :exception
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
initializer "action_controller.eager_load_actions" do
|
84
|
+
ActiveSupport.on_load(:after_initialize) do
|
85
|
+
ActionController::Metal.descendants.each(&:action_methods) if config.eager_load
|
86
|
+
end
|
87
|
+
end
|
76
88
|
end
|
77
89
|
end
|
@@ -1,7 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "rack/session/abstract/id"
|
2
4
|
require "active_support/core_ext/hash/conversions"
|
3
5
|
require "active_support/core_ext/object/to_query"
|
4
6
|
require "active_support/core_ext/module/anonymous"
|
7
|
+
require "active_support/core_ext/module/redefine_method"
|
5
8
|
require "active_support/core_ext/hash/keys"
|
6
9
|
require "active_support/testing/constant_lookup"
|
7
10
|
require "action_controller/template_assertions"
|
@@ -17,7 +20,7 @@ module ActionController
|
|
17
20
|
# the database on the main thread, so they could open a txn, then the
|
18
21
|
# controller thread will open a new connection and try to access data
|
19
22
|
# that's only visible to the main thread's txn. This is the problem in #23483.
|
20
|
-
|
23
|
+
silence_redefinition_of_method :new_controller_thread
|
21
24
|
def new_controller_thread # :nodoc:
|
22
25
|
yield
|
23
26
|
end
|
@@ -253,7 +256,7 @@ module ActionController
|
|
253
256
|
#
|
254
257
|
# def test_create
|
255
258
|
# json = {book: { title: "Love Hina" }}.to_json
|
256
|
-
# post :create, json
|
259
|
+
# post :create, body: json
|
257
260
|
# end
|
258
261
|
#
|
259
262
|
# == Special instance variables
|
@@ -454,13 +457,10 @@ module ActionController
|
|
454
457
|
# respectively which will make tests more expressive.
|
455
458
|
#
|
456
459
|
# Note that the request method is not verified.
|
457
|
-
def process(action, method: "GET", params:
|
460
|
+
def process(action, method: "GET", params: nil, session: nil, body: nil, flash: {}, format: nil, xhr: false, as: nil)
|
458
461
|
check_required_ivars
|
459
462
|
|
460
|
-
|
461
|
-
@request.set_header "RAW_POST_DATA", body
|
462
|
-
end
|
463
|
-
|
463
|
+
action = action.to_s.dup
|
464
464
|
http_method = method.to_s.upcase
|
465
465
|
|
466
466
|
@html_document = nil
|
@@ -475,6 +475,10 @@ module ActionController
|
|
475
475
|
@response.request = @request
|
476
476
|
@controller.recycle!
|
477
477
|
|
478
|
+
if body
|
479
|
+
@request.set_header "RAW_POST_DATA", body
|
480
|
+
end
|
481
|
+
|
478
482
|
@request.set_header "REQUEST_METHOD", http_method
|
479
483
|
|
480
484
|
if as
|
@@ -482,17 +486,17 @@ module ActionController
|
|
482
486
|
format ||= as
|
483
487
|
end
|
484
488
|
|
485
|
-
parameters = params.symbolize_keys
|
489
|
+
parameters = (params || {}).symbolize_keys
|
486
490
|
|
487
491
|
if format
|
488
492
|
parameters[:format] = format
|
489
493
|
end
|
490
494
|
|
491
|
-
generated_extras = @routes.generate_extras(parameters.merge(controller: controller_class_name, action: action
|
495
|
+
generated_extras = @routes.generate_extras(parameters.merge(controller: controller_class_name, action: action))
|
492
496
|
generated_path = generated_path(generated_extras)
|
493
497
|
query_string_keys = query_parameter_names(generated_extras)
|
494
498
|
|
495
|
-
@request.assign_parameters(@routes, controller_class_name, action
|
499
|
+
@request.assign_parameters(@routes, controller_class_name, action, parameters, generated_path, query_string_keys)
|
496
500
|
|
497
501
|
@request.session.update(session) if session
|
498
502
|
@request.flash.update(flash || {})
|
@@ -601,6 +605,8 @@ module ActionController
|
|
601
605
|
env.delete "action_dispatch.request.query_parameters"
|
602
606
|
env.delete "action_dispatch.request.request_parameters"
|
603
607
|
env["rack.input"] = StringIO.new
|
608
|
+
env.delete "CONTENT_LENGTH"
|
609
|
+
env.delete "RAW_POST_DATA"
|
604
610
|
env
|
605
611
|
end
|
606
612
|
|
data/lib/action_dispatch.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
#--
|
2
|
-
# Copyright (c) 2004-
|
4
|
+
# Copyright (c) 2004-2018 David Heinemeier Hansson
|
3
5
|
#
|
4
6
|
# Permission is hereby granted, free of charge, to any person obtaining
|
5
7
|
# a copy of this software and associated documentation files (the
|
@@ -40,6 +42,7 @@ module ActionDispatch
|
|
40
42
|
|
41
43
|
eager_autoload do
|
42
44
|
autoload_under "http" do
|
45
|
+
autoload :ContentSecurityPolicy
|
43
46
|
autoload :Request
|
44
47
|
autoload :Response
|
45
48
|
end
|
@@ -80,10 +83,11 @@ module ActionDispatch
|
|
80
83
|
end
|
81
84
|
|
82
85
|
module Session
|
83
|
-
autoload :AbstractStore,
|
84
|
-
autoload :
|
85
|
-
autoload :
|
86
|
-
autoload :
|
86
|
+
autoload :AbstractStore, "action_dispatch/middleware/session/abstract_store"
|
87
|
+
autoload :AbstractSecureStore, "action_dispatch/middleware/session/abstract_store"
|
88
|
+
autoload :CookieStore, "action_dispatch/middleware/session/cookie_store"
|
89
|
+
autoload :MemCacheStore, "action_dispatch/middleware/session/mem_cache_store"
|
90
|
+
autoload :CacheStore, "action_dispatch/middleware/session/cache_store"
|
87
91
|
end
|
88
92
|
|
89
93
|
mattr_accessor :test_app
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActionDispatch
|
2
4
|
module Http
|
3
5
|
module Cache
|
@@ -95,7 +97,7 @@ module ActionDispatch
|
|
95
97
|
# support strong ETags and will ignore weak ETags entirely.
|
96
98
|
#
|
97
99
|
# Weak ETags are what we almost always need, so they're the default.
|
98
|
-
# Check out
|
100
|
+
# Check out #strong_etag= to provide a strong ETag validator.
|
99
101
|
def etag=(weak_validators)
|
100
102
|
self.weak_etag = weak_validators
|
101
103
|
end
|
@@ -131,7 +133,7 @@ module ActionDispatch
|
|
131
133
|
end
|
132
134
|
|
133
135
|
def generate_strong_etag(validators)
|
134
|
-
%("#{Digest
|
136
|
+
%("#{ActiveSupport::Digest.hexdigest(ActiveSupport::Cache.expand_cache_key(validators))}")
|
135
137
|
end
|
136
138
|
|
137
139
|
def cache_control_segments
|
@@ -164,19 +166,23 @@ module ActionDispatch
|
|
164
166
|
@cache_control = cache_control_headers
|
165
167
|
end
|
166
168
|
|
167
|
-
def handle_conditional_get!
|
168
|
-
if etag? || last_modified? || !@cache_control.empty?
|
169
|
-
set_conditional_cache_control!(@cache_control)
|
170
|
-
end
|
171
|
-
end
|
172
|
-
|
173
169
|
DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate".freeze
|
174
170
|
NO_CACHE = "no-cache".freeze
|
175
171
|
PUBLIC = "public".freeze
|
176
172
|
PRIVATE = "private".freeze
|
177
173
|
MUST_REVALIDATE = "must-revalidate".freeze
|
178
174
|
|
179
|
-
def
|
175
|
+
def handle_conditional_get!
|
176
|
+
# Normally default cache control setting is handled by ETag
|
177
|
+
# middleware. But, if an etag is already set, the middleware
|
178
|
+
# defaults to `no-cache` unless a default `Cache-Control` value is
|
179
|
+
# previously set. So, set a default one here.
|
180
|
+
if (etag? || last_modified?) && !self._cache_control
|
181
|
+
self._cache_control = DEFAULT_CACHE_CONTROL
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def merge_and_normalize_cache_control!(cache_control)
|
180
186
|
control = {}
|
181
187
|
cc_headers = cache_control_headers
|
182
188
|
if extras = cc_headers.delete(:extras)
|
@@ -189,12 +195,14 @@ module ActionDispatch
|
|
189
195
|
control.merge! cache_control
|
190
196
|
|
191
197
|
if control.empty?
|
192
|
-
|
198
|
+
# Let middleware handle default behavior
|
193
199
|
elsif control[:no_cache]
|
194
|
-
|
195
|
-
if control[:
|
196
|
-
|
197
|
-
|
200
|
+
options = []
|
201
|
+
options << PUBLIC if control[:public]
|
202
|
+
options << NO_CACHE
|
203
|
+
options.concat(control[:extras]) if control[:extras]
|
204
|
+
|
205
|
+
self._cache_control = options.join(", ")
|
198
206
|
else
|
199
207
|
extras = control[:extras]
|
200
208
|
max_age = control[:max_age]
|
@@ -0,0 +1,272 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/object/deep_dup"
|
4
|
+
|
5
|
+
module ActionDispatch #:nodoc:
|
6
|
+
class ContentSecurityPolicy
|
7
|
+
class Middleware
|
8
|
+
CONTENT_TYPE = "Content-Type".freeze
|
9
|
+
POLICY = "Content-Security-Policy".freeze
|
10
|
+
POLICY_REPORT_ONLY = "Content-Security-Policy-Report-Only".freeze
|
11
|
+
|
12
|
+
def initialize(app)
|
13
|
+
@app = app
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(env)
|
17
|
+
request = ActionDispatch::Request.new env
|
18
|
+
_, headers, _ = response = @app.call(env)
|
19
|
+
|
20
|
+
return response unless html_response?(headers)
|
21
|
+
return response if policy_present?(headers)
|
22
|
+
|
23
|
+
if policy = request.content_security_policy
|
24
|
+
nonce = request.content_security_policy_nonce
|
25
|
+
context = request.controller_instance || request
|
26
|
+
headers[header_name(request)] = policy.build(context, nonce)
|
27
|
+
end
|
28
|
+
|
29
|
+
response
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def html_response?(headers)
|
35
|
+
if content_type = headers[CONTENT_TYPE]
|
36
|
+
content_type =~ /html/
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def header_name(request)
|
41
|
+
if request.content_security_policy_report_only
|
42
|
+
POLICY_REPORT_ONLY
|
43
|
+
else
|
44
|
+
POLICY
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def policy_present?(headers)
|
49
|
+
headers[POLICY] || headers[POLICY_REPORT_ONLY]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
module Request
|
54
|
+
POLICY = "action_dispatch.content_security_policy".freeze
|
55
|
+
POLICY_REPORT_ONLY = "action_dispatch.content_security_policy_report_only".freeze
|
56
|
+
NONCE_GENERATOR = "action_dispatch.content_security_policy_nonce_generator".freeze
|
57
|
+
NONCE = "action_dispatch.content_security_policy_nonce".freeze
|
58
|
+
|
59
|
+
def content_security_policy
|
60
|
+
get_header(POLICY)
|
61
|
+
end
|
62
|
+
|
63
|
+
def content_security_policy=(policy)
|
64
|
+
set_header(POLICY, policy)
|
65
|
+
end
|
66
|
+
|
67
|
+
def content_security_policy_report_only
|
68
|
+
get_header(POLICY_REPORT_ONLY)
|
69
|
+
end
|
70
|
+
|
71
|
+
def content_security_policy_report_only=(value)
|
72
|
+
set_header(POLICY_REPORT_ONLY, value)
|
73
|
+
end
|
74
|
+
|
75
|
+
def content_security_policy_nonce_generator
|
76
|
+
get_header(NONCE_GENERATOR)
|
77
|
+
end
|
78
|
+
|
79
|
+
def content_security_policy_nonce_generator=(generator)
|
80
|
+
set_header(NONCE_GENERATOR, generator)
|
81
|
+
end
|
82
|
+
|
83
|
+
def content_security_policy_nonce
|
84
|
+
if content_security_policy_nonce_generator
|
85
|
+
if nonce = get_header(NONCE)
|
86
|
+
nonce
|
87
|
+
else
|
88
|
+
set_header(NONCE, generate_content_security_policy_nonce)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def generate_content_security_policy_nonce
|
96
|
+
content_security_policy_nonce_generator.call(self)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
MAPPINGS = {
|
101
|
+
self: "'self'",
|
102
|
+
unsafe_eval: "'unsafe-eval'",
|
103
|
+
unsafe_inline: "'unsafe-inline'",
|
104
|
+
none: "'none'",
|
105
|
+
http: "http:",
|
106
|
+
https: "https:",
|
107
|
+
data: "data:",
|
108
|
+
mediastream: "mediastream:",
|
109
|
+
blob: "blob:",
|
110
|
+
filesystem: "filesystem:",
|
111
|
+
report_sample: "'report-sample'",
|
112
|
+
strict_dynamic: "'strict-dynamic'",
|
113
|
+
ws: "ws:",
|
114
|
+
wss: "wss:"
|
115
|
+
}.freeze
|
116
|
+
|
117
|
+
DIRECTIVES = {
|
118
|
+
base_uri: "base-uri",
|
119
|
+
child_src: "child-src",
|
120
|
+
connect_src: "connect-src",
|
121
|
+
default_src: "default-src",
|
122
|
+
font_src: "font-src",
|
123
|
+
form_action: "form-action",
|
124
|
+
frame_ancestors: "frame-ancestors",
|
125
|
+
frame_src: "frame-src",
|
126
|
+
img_src: "img-src",
|
127
|
+
manifest_src: "manifest-src",
|
128
|
+
media_src: "media-src",
|
129
|
+
object_src: "object-src",
|
130
|
+
script_src: "script-src",
|
131
|
+
style_src: "style-src",
|
132
|
+
worker_src: "worker-src"
|
133
|
+
}.freeze
|
134
|
+
|
135
|
+
NONCE_DIRECTIVES = %w[script-src].freeze
|
136
|
+
|
137
|
+
private_constant :MAPPINGS, :DIRECTIVES, :NONCE_DIRECTIVES
|
138
|
+
|
139
|
+
attr_reader :directives
|
140
|
+
|
141
|
+
def initialize
|
142
|
+
@directives = {}
|
143
|
+
yield self if block_given?
|
144
|
+
end
|
145
|
+
|
146
|
+
def initialize_copy(other)
|
147
|
+
@directives = other.directives.deep_dup
|
148
|
+
end
|
149
|
+
|
150
|
+
DIRECTIVES.each do |name, directive|
|
151
|
+
define_method(name) do |*sources|
|
152
|
+
if sources.first
|
153
|
+
@directives[directive] = apply_mappings(sources)
|
154
|
+
else
|
155
|
+
@directives.delete(directive)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def block_all_mixed_content(enabled = true)
|
161
|
+
if enabled
|
162
|
+
@directives["block-all-mixed-content"] = true
|
163
|
+
else
|
164
|
+
@directives.delete("block-all-mixed-content")
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def plugin_types(*types)
|
169
|
+
if types.first
|
170
|
+
@directives["plugin-types"] = types
|
171
|
+
else
|
172
|
+
@directives.delete("plugin-types")
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def report_uri(uri)
|
177
|
+
@directives["report-uri"] = [uri]
|
178
|
+
end
|
179
|
+
|
180
|
+
def require_sri_for(*types)
|
181
|
+
if types.first
|
182
|
+
@directives["require-sri-for"] = types
|
183
|
+
else
|
184
|
+
@directives.delete("require-sri-for")
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def sandbox(*values)
|
189
|
+
if values.empty?
|
190
|
+
@directives["sandbox"] = true
|
191
|
+
elsif values.first
|
192
|
+
@directives["sandbox"] = values
|
193
|
+
else
|
194
|
+
@directives.delete("sandbox")
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def upgrade_insecure_requests(enabled = true)
|
199
|
+
if enabled
|
200
|
+
@directives["upgrade-insecure-requests"] = true
|
201
|
+
else
|
202
|
+
@directives.delete("upgrade-insecure-requests")
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def build(context = nil, nonce = nil)
|
207
|
+
build_directives(context, nonce).compact.join("; ")
|
208
|
+
end
|
209
|
+
|
210
|
+
private
|
211
|
+
def apply_mappings(sources)
|
212
|
+
sources.map do |source|
|
213
|
+
case source
|
214
|
+
when Symbol
|
215
|
+
apply_mapping(source)
|
216
|
+
when String, Proc
|
217
|
+
source
|
218
|
+
else
|
219
|
+
raise ArgumentError, "Invalid content security policy source: #{source.inspect}"
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def apply_mapping(source)
|
225
|
+
MAPPINGS.fetch(source) do
|
226
|
+
raise ArgumentError, "Unknown content security policy source mapping: #{source.inspect}"
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def build_directives(context, nonce)
|
231
|
+
@directives.map do |directive, sources|
|
232
|
+
if sources.is_a?(Array)
|
233
|
+
if nonce && nonce_directive?(directive)
|
234
|
+
"#{directive} #{build_directive(sources, context).join(' ')} 'nonce-#{nonce}'"
|
235
|
+
else
|
236
|
+
"#{directive} #{build_directive(sources, context).join(' ')}"
|
237
|
+
end
|
238
|
+
elsif sources
|
239
|
+
directive
|
240
|
+
else
|
241
|
+
nil
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def build_directive(sources, context)
|
247
|
+
sources.map { |source| resolve_source(source, context) }
|
248
|
+
end
|
249
|
+
|
250
|
+
def resolve_source(source, context)
|
251
|
+
case source
|
252
|
+
when String
|
253
|
+
source
|
254
|
+
when Symbol
|
255
|
+
source.to_s
|
256
|
+
when Proc
|
257
|
+
if context.nil?
|
258
|
+
raise RuntimeError, "Missing context for the dynamic content security policy source: #{source.inspect}"
|
259
|
+
else
|
260
|
+
resolved = context.instance_exec(&source)
|
261
|
+
resolved.is_a?(Symbol) ? apply_mapping(resolved) : resolved
|
262
|
+
end
|
263
|
+
else
|
264
|
+
raise RuntimeError, "Unexpected content security policy source: #{source.inspect}"
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
def nonce_directive?(directive)
|
269
|
+
NONCE_DIRECTIVES.include?(directive)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|