actionpack 6.0.4.7 → 6.1.0.rc1
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 +235 -331
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/lib/abstract_controller/base.rb +35 -2
- data/lib/abstract_controller/callbacks.rb +2 -2
- data/lib/abstract_controller/helpers.rb +105 -90
- data/lib/abstract_controller/rendering.rb +9 -9
- data/lib/abstract_controller/translation.rb +8 -2
- data/lib/abstract_controller.rb +1 -0
- data/lib/action_controller/api.rb +2 -2
- data/lib/action_controller/base.rb +4 -2
- data/lib/action_controller/caching.rb +0 -1
- data/lib/action_controller/log_subscriber.rb +3 -3
- data/lib/action_controller/metal/conditional_get.rb +10 -2
- data/lib/action_controller/metal/content_security_policy.rb +1 -1
- data/lib/action_controller/metal/data_streaming.rb +1 -1
- data/lib/action_controller/metal/etag_with_template_digest.rb +2 -4
- data/lib/action_controller/metal/exceptions.rb +33 -0
- data/lib/action_controller/metal/feature_policy.rb +46 -0
- data/lib/action_controller/metal/head.rb +7 -4
- data/lib/action_controller/metal/helpers.rb +11 -1
- data/lib/action_controller/metal/http_authentication.rb +5 -3
- data/lib/action_controller/metal/implicit_render.rb +1 -1
- data/lib/action_controller/metal/instrumentation.rb +11 -9
- data/lib/action_controller/metal/live.rb +1 -1
- data/lib/action_controller/metal/logging.rb +20 -0
- data/lib/action_controller/metal/mime_responds.rb +6 -2
- data/lib/action_controller/metal/parameter_encoding.rb +35 -4
- data/lib/action_controller/metal/params_wrapper.rb +16 -11
- data/lib/action_controller/metal/redirecting.rb +1 -1
- data/lib/action_controller/metal/rendering.rb +6 -0
- data/lib/action_controller/metal/request_forgery_protection.rb +1 -1
- data/lib/action_controller/metal/rescue.rb +1 -1
- data/lib/action_controller/metal/strong_parameters.rb +103 -15
- data/lib/action_controller/metal.rb +2 -2
- data/lib/action_controller/renderer.rb +24 -13
- data/lib/action_controller/test_case.rb +62 -56
- data/lib/action_controller.rb +2 -3
- data/lib/action_dispatch/http/cache.rb +12 -10
- data/lib/action_dispatch/http/content_security_policy.rb +5 -1
- data/lib/action_dispatch/http/feature_policy.rb +168 -0
- data/lib/action_dispatch/http/filter_parameters.rb +1 -1
- data/lib/action_dispatch/http/filter_redirect.rb +1 -1
- data/lib/action_dispatch/http/headers.rb +3 -2
- data/lib/action_dispatch/http/mime_negotiation.rb +14 -8
- data/lib/action_dispatch/http/mime_type.rb +29 -16
- data/lib/action_dispatch/http/parameters.rb +1 -19
- data/lib/action_dispatch/http/request.rb +24 -8
- data/lib/action_dispatch/http/response.rb +17 -16
- data/lib/action_dispatch/http/url.rb +3 -2
- data/lib/action_dispatch/journey/formatter.rb +53 -28
- data/lib/action_dispatch/journey/gtg/builder.rb +22 -36
- data/lib/action_dispatch/journey/gtg/simulator.rb +8 -7
- data/lib/action_dispatch/journey/gtg/transition_table.rb +6 -4
- data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
- data/lib/action_dispatch/journey/nodes/node.rb +4 -3
- data/lib/action_dispatch/journey/parser.rb +13 -13
- data/lib/action_dispatch/journey/parser.y +1 -1
- data/lib/action_dispatch/journey/path/pattern.rb +13 -18
- data/lib/action_dispatch/journey/route.rb +7 -18
- data/lib/action_dispatch/journey/router/utils.rb +6 -4
- data/lib/action_dispatch/journey/router.rb +26 -30
- data/lib/action_dispatch/journey.rb +0 -2
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +1 -1
- data/lib/action_dispatch/middleware/cookies.rb +67 -32
- data/lib/action_dispatch/middleware/debug_exceptions.rb +8 -15
- data/lib/action_dispatch/middleware/debug_view.rb +1 -1
- data/lib/action_dispatch/middleware/exception_wrapper.rb +28 -16
- data/lib/action_dispatch/middleware/executor.rb +1 -1
- data/lib/action_dispatch/middleware/host_authorization.rb +35 -35
- data/lib/action_dispatch/middleware/remote_ip.rb +5 -4
- data/lib/action_dispatch/middleware/request_id.rb +4 -5
- data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -2
- data/lib/action_dispatch/middleware/session/cookie_store.rb +2 -2
- data/lib/action_dispatch/middleware/ssl.rb +9 -6
- data/lib/action_dispatch/middleware/stack.rb +18 -0
- data/lib/action_dispatch/middleware/static.rb +154 -93
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +18 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +2 -5
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +2 -3
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +88 -8
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +12 -1
- data/lib/action_dispatch/railtie.rb +3 -2
- data/lib/action_dispatch/request/session.rb +2 -8
- data/lib/action_dispatch/request/utils.rb +26 -2
- data/lib/action_dispatch/routing/inspector.rb +8 -7
- data/lib/action_dispatch/routing/mapper.rb +102 -71
- data/lib/action_dispatch/routing/polymorphic_routes.rb +16 -19
- data/lib/action_dispatch/routing/redirection.rb +3 -3
- data/lib/action_dispatch/routing/route_set.rb +49 -41
- data/lib/action_dispatch/system_test_case.rb +29 -24
- data/lib/action_dispatch/system_testing/browser.rb +33 -27
- data/lib/action_dispatch/system_testing/driver.rb +6 -7
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +47 -6
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +4 -7
- data/lib/action_dispatch/testing/assertions/response.rb +2 -4
- data/lib/action_dispatch/testing/assertions/routing.rb +5 -5
- data/lib/action_dispatch/testing/assertions.rb +1 -1
- data/lib/action_dispatch/testing/integration.rb +38 -27
- data/lib/action_dispatch/testing/test_process.rb +29 -4
- data/lib/action_dispatch/testing/test_request.rb +3 -3
- data/lib/action_dispatch.rb +3 -2
- data/lib/action_pack/gem_version.rb +3 -3
- data/lib/action_pack.rb +1 -1
- metadata +23 -24
- data/lib/action_controller/metal/force_ssl.rb +0 -58
- data/lib/action_dispatch/http/parameter_filter.rb +0 -12
- data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
- data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
- data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -119
@@ -126,7 +126,7 @@ module ActionController
|
|
126
126
|
# ==== Returns
|
127
127
|
# * <tt>string</tt>
|
128
128
|
def self.controller_name
|
129
|
-
@controller_name ||= name.demodulize.
|
129
|
+
@controller_name ||= (name.demodulize.delete_suffix("Controller").underscore unless anonymous?)
|
130
130
|
end
|
131
131
|
|
132
132
|
def self.make_response!(request)
|
@@ -135,7 +135,7 @@ module ActionController
|
|
135
135
|
end
|
136
136
|
end
|
137
137
|
|
138
|
-
def self.
|
138
|
+
def self.action_encoding_template(action) # :nodoc:
|
139
139
|
false
|
140
140
|
end
|
141
141
|
|
@@ -65,7 +65,7 @@ module ActionController
|
|
65
65
|
def initialize(controller, env, defaults)
|
66
66
|
@controller = controller
|
67
67
|
@defaults = defaults
|
68
|
-
@env = normalize_keys defaults
|
68
|
+
@env = normalize_keys defaults, env
|
69
69
|
end
|
70
70
|
|
71
71
|
# Render templates with any options from ActionController::Base#render_to_string.
|
@@ -82,8 +82,12 @@ module ActionController
|
|
82
82
|
# need to call <tt>.to_json</tt> on the object you want to render.
|
83
83
|
# * <tt>:body</tt> - Renders provided text and sets content type of <tt>text/plain</tt>.
|
84
84
|
#
|
85
|
-
# If no <tt>options</tt> hash is passed or if <tt>:update</tt> is specified,
|
86
|
-
#
|
85
|
+
# If no <tt>options</tt> hash is passed or if <tt>:update</tt> is specified, then:
|
86
|
+
#
|
87
|
+
# If an object responding to `render_in` is passed, `render_in` is called on the object,
|
88
|
+
# passing in the current view context.
|
89
|
+
#
|
90
|
+
# Otherwise, a partial is rendered using the second parameter as the locals hash.
|
87
91
|
def render(*args)
|
88
92
|
raise "missing controller" unless controller
|
89
93
|
|
@@ -95,11 +99,18 @@ module ActionController
|
|
95
99
|
instance.set_response! controller.make_response!(request)
|
96
100
|
instance.render_to_string(*args)
|
97
101
|
end
|
102
|
+
alias_method :render_to_string, :render # :nodoc:
|
98
103
|
|
99
104
|
private
|
100
|
-
def normalize_keys(env)
|
105
|
+
def normalize_keys(defaults, env)
|
101
106
|
new_env = {}
|
102
107
|
env.each_pair { |k, v| new_env[rack_key_for(k)] = rack_value_for(k, v) }
|
108
|
+
|
109
|
+
defaults.each_pair do |k, v|
|
110
|
+
key = rack_key_for(k)
|
111
|
+
new_env[key] = rack_value_for(k, v) unless new_env.key?(key)
|
112
|
+
end
|
113
|
+
|
103
114
|
new_env["rack.url_scheme"] = new_env["HTTPS"] == "on" ? "https" : "http"
|
104
115
|
new_env
|
105
116
|
end
|
@@ -112,19 +123,19 @@ module ActionController
|
|
112
123
|
input: "rack.input"
|
113
124
|
}
|
114
125
|
|
115
|
-
IDENTITY = ->(_) { _ }
|
116
|
-
|
117
|
-
RACK_VALUE_TRANSLATION = {
|
118
|
-
https: ->(v) { v ? "on" : "off" },
|
119
|
-
method: ->(v) { -v.upcase },
|
120
|
-
}
|
121
|
-
|
122
126
|
def rack_key_for(key)
|
123
|
-
RACK_KEY_TRANSLATION
|
127
|
+
RACK_KEY_TRANSLATION[key] || key.to_s
|
124
128
|
end
|
125
129
|
|
126
130
|
def rack_value_for(key, value)
|
127
|
-
|
131
|
+
case key
|
132
|
+
when :https
|
133
|
+
value ? "on" : "off"
|
134
|
+
when :method
|
135
|
+
-value.upcase
|
136
|
+
else
|
137
|
+
value
|
138
|
+
end
|
128
139
|
end
|
129
140
|
end
|
130
141
|
end
|
@@ -84,7 +84,7 @@ module ActionController
|
|
84
84
|
value = value.to_param
|
85
85
|
end
|
86
86
|
|
87
|
-
path_parameters[key] = value
|
87
|
+
path_parameters[key.to_sym] = value
|
88
88
|
end
|
89
89
|
end
|
90
90
|
|
@@ -492,57 +492,8 @@ module ActionController
|
|
492
492
|
parameters[:format] = format
|
493
493
|
end
|
494
494
|
|
495
|
-
|
496
|
-
|
497
|
-
query_string_keys = query_parameter_names(generated_extras)
|
498
|
-
|
499
|
-
@request.assign_parameters(@routes, controller_class_name, action, parameters, generated_path, query_string_keys)
|
500
|
-
|
501
|
-
@request.session.update(session) if session
|
502
|
-
@request.flash.update(flash || {})
|
503
|
-
|
504
|
-
if xhr
|
505
|
-
@request.set_header "HTTP_X_REQUESTED_WITH", "XMLHttpRequest"
|
506
|
-
@request.fetch_header("HTTP_ACCEPT") do |k|
|
507
|
-
@request.set_header k, [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(", ")
|
508
|
-
end
|
509
|
-
end
|
510
|
-
|
511
|
-
@request.fetch_header("SCRIPT_NAME") do |k|
|
512
|
-
@request.set_header k, @controller.config.relative_url_root
|
513
|
-
end
|
514
|
-
|
515
|
-
begin
|
516
|
-
@controller.recycle!
|
517
|
-
@controller.dispatch(action, @request, @response)
|
518
|
-
ensure
|
519
|
-
@request = @controller.request
|
520
|
-
@response = @controller.response
|
521
|
-
|
522
|
-
if @request.have_cookie_jar?
|
523
|
-
unless @request.cookie_jar.committed?
|
524
|
-
@request.cookie_jar.write(@response)
|
525
|
-
cookies.update(@request.cookie_jar.instance_variable_get(:@cookies))
|
526
|
-
end
|
527
|
-
end
|
528
|
-
@response.prepare!
|
529
|
-
|
530
|
-
if flash_value = @request.flash.to_session_value
|
531
|
-
@request.session["flash"] = flash_value
|
532
|
-
else
|
533
|
-
@request.session.delete("flash")
|
534
|
-
end
|
535
|
-
|
536
|
-
if xhr
|
537
|
-
@request.delete_header "HTTP_X_REQUESTED_WITH"
|
538
|
-
@request.delete_header "HTTP_ACCEPT"
|
539
|
-
end
|
540
|
-
@request.query_string = ""
|
541
|
-
|
542
|
-
@response.sent!
|
543
|
-
end
|
544
|
-
|
545
|
-
@response
|
495
|
+
setup_request(controller_class_name, action, parameters, session, flash, xhr)
|
496
|
+
process_controller_response(action, cookies, xhr)
|
546
497
|
end
|
547
498
|
|
548
499
|
def controller_class_name
|
@@ -598,11 +549,66 @@ module ActionController
|
|
598
549
|
end
|
599
550
|
|
600
551
|
private
|
552
|
+
def setup_request(controller_class_name, action, parameters, session, flash, xhr)
|
553
|
+
generated_extras = @routes.generate_extras(parameters.merge(controller: controller_class_name, action: action))
|
554
|
+
generated_path = generated_path(generated_extras)
|
555
|
+
query_string_keys = query_parameter_names(generated_extras)
|
556
|
+
|
557
|
+
@request.assign_parameters(@routes, controller_class_name, action, parameters, generated_path, query_string_keys)
|
558
|
+
|
559
|
+
@request.session.update(session) if session
|
560
|
+
@request.flash.update(flash || {})
|
561
|
+
|
562
|
+
if xhr
|
563
|
+
@request.set_header "HTTP_X_REQUESTED_WITH", "XMLHttpRequest"
|
564
|
+
@request.fetch_header("HTTP_ACCEPT") do |k|
|
565
|
+
@request.set_header k, [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(", ")
|
566
|
+
end
|
567
|
+
end
|
568
|
+
|
569
|
+
@request.fetch_header("SCRIPT_NAME") do |k|
|
570
|
+
@request.set_header k, @controller.config.relative_url_root
|
571
|
+
end
|
572
|
+
end
|
573
|
+
|
574
|
+
def process_controller_response(action, cookies, xhr)
|
575
|
+
begin
|
576
|
+
@controller.recycle!
|
577
|
+
@controller.dispatch(action, @request, @response)
|
578
|
+
ensure
|
579
|
+
@request = @controller.request
|
580
|
+
@response = @controller.response
|
581
|
+
|
582
|
+
if @request.have_cookie_jar?
|
583
|
+
unless @request.cookie_jar.committed?
|
584
|
+
@request.cookie_jar.write(@response)
|
585
|
+
cookies.update(@request.cookie_jar.instance_variable_get(:@cookies))
|
586
|
+
end
|
587
|
+
end
|
588
|
+
@response.prepare!
|
589
|
+
|
590
|
+
if flash_value = @request.flash.to_session_value
|
591
|
+
@request.session["flash"] = flash_value
|
592
|
+
else
|
593
|
+
@request.session.delete("flash")
|
594
|
+
end
|
595
|
+
|
596
|
+
if xhr
|
597
|
+
@request.delete_header "HTTP_X_REQUESTED_WITH"
|
598
|
+
@request.delete_header "HTTP_ACCEPT"
|
599
|
+
end
|
600
|
+
@request.query_string = ""
|
601
|
+
|
602
|
+
@response.sent!
|
603
|
+
end
|
604
|
+
|
605
|
+
@response
|
606
|
+
end
|
607
|
+
|
601
608
|
def scrub_env!(env)
|
602
|
-
env.delete_if
|
603
|
-
|
604
|
-
|
605
|
-
env.delete "action_dispatch.request.request_parameters"
|
609
|
+
env.delete_if do |k, _|
|
610
|
+
k.start_with?("rack.request", "action_dispatch.request", "action_dispatch.rescue")
|
611
|
+
end
|
606
612
|
env["rack.input"] = StringIO.new
|
607
613
|
env.delete "CONTENT_LENGTH"
|
608
614
|
env.delete "RAW_POST_DATA"
|
data/lib/action_controller.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_support/rails"
|
4
3
|
require "abstract_controller"
|
5
4
|
require "action_dispatch"
|
6
5
|
require "action_controller/metal/strong_parameters"
|
@@ -11,7 +10,6 @@ module ActionController
|
|
11
10
|
autoload :API
|
12
11
|
autoload :Base
|
13
12
|
autoload :Metal
|
14
|
-
autoload :Middleware
|
15
13
|
autoload :Renderer
|
16
14
|
autoload :FormBuilder
|
17
15
|
|
@@ -31,14 +29,15 @@ module ActionController
|
|
31
29
|
autoload :DefaultHeaders
|
32
30
|
autoload :EtagWithTemplateDigest
|
33
31
|
autoload :EtagWithFlash
|
32
|
+
autoload :FeaturePolicy
|
34
33
|
autoload :Flash
|
35
|
-
autoload :ForceSSL
|
36
34
|
autoload :Head
|
37
35
|
autoload :Helpers
|
38
36
|
autoload :HttpAuthentication
|
39
37
|
autoload :BasicImplicitRender
|
40
38
|
autoload :ImplicitRender
|
41
39
|
autoload :Instrumentation
|
40
|
+
autoload :Logging
|
42
41
|
autoload :MimeResponds
|
43
42
|
autoload :ParamsWrapper
|
44
43
|
autoload :Redirecting
|
@@ -114,7 +114,7 @@ module ActionDispatch
|
|
114
114
|
|
115
115
|
# True if an ETag is set and it's a weak validator (preceded with W/)
|
116
116
|
def weak_etag?
|
117
|
-
etag? && etag.
|
117
|
+
etag? && etag.start_with?('W/"')
|
118
118
|
end
|
119
119
|
|
120
120
|
# True if an ETag is set and it isn't a weak validator (not preceded with W/)
|
@@ -125,7 +125,7 @@ module ActionDispatch
|
|
125
125
|
private
|
126
126
|
DATE = "Date"
|
127
127
|
LAST_MODIFIED = "Last-Modified"
|
128
|
-
SPECIAL_KEYS = Set.new(%w[extras no-cache max-age public private must-revalidate])
|
128
|
+
SPECIAL_KEYS = Set.new(%w[extras no-store no-cache max-age public private must-revalidate])
|
129
129
|
|
130
130
|
def generate_weak_etag(validators)
|
131
131
|
"W/#{generate_strong_etag(validators)}"
|
@@ -150,8 +150,8 @@ module ActionDispatch
|
|
150
150
|
directive, argument = segment.split("=", 2)
|
151
151
|
|
152
152
|
if SPECIAL_KEYS.include? directive
|
153
|
-
|
154
|
-
cache_control[
|
153
|
+
directive.tr!("-", "_")
|
154
|
+
cache_control[directive.to_sym] = argument || true
|
155
155
|
else
|
156
156
|
cache_control[:extras] ||= []
|
157
157
|
cache_control[:extras] << segment
|
@@ -166,6 +166,7 @@ module ActionDispatch
|
|
166
166
|
end
|
167
167
|
|
168
168
|
DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate"
|
169
|
+
NO_STORE = "no-store"
|
169
170
|
NO_CACHE = "no-cache"
|
170
171
|
PUBLIC = "public"
|
171
172
|
PRIVATE = "private"
|
@@ -182,19 +183,20 @@ module ActionDispatch
|
|
182
183
|
end
|
183
184
|
|
184
185
|
def merge_and_normalize_cache_control!(cache_control)
|
185
|
-
control =
|
186
|
-
|
187
|
-
if
|
186
|
+
control = cache_control_headers
|
187
|
+
|
188
|
+
return if control.empty? && cache_control.empty? # Let middleware handle default behavior
|
189
|
+
|
190
|
+
if extras = control.delete(:extras)
|
188
191
|
cache_control[:extras] ||= []
|
189
192
|
cache_control[:extras] += extras
|
190
193
|
cache_control[:extras].uniq!
|
191
194
|
end
|
192
195
|
|
193
|
-
control.merge! cc_headers
|
194
196
|
control.merge! cache_control
|
195
197
|
|
196
|
-
if control
|
197
|
-
|
198
|
+
if control[:no_store]
|
199
|
+
self._cache_control = NO_STORE
|
198
200
|
elsif control[:no_cache]
|
199
201
|
options = []
|
200
202
|
options << PUBLIC if control[:public]
|
@@ -33,7 +33,7 @@ module ActionDispatch #:nodoc:
|
|
33
33
|
private
|
34
34
|
def html_response?(headers)
|
35
35
|
if content_type = headers[CONTENT_TYPE]
|
36
|
-
|
36
|
+
/html/.match?(content_type)
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
@@ -137,7 +137,11 @@ module ActionDispatch #:nodoc:
|
|
137
137
|
object_src: "object-src",
|
138
138
|
prefetch_src: "prefetch-src",
|
139
139
|
script_src: "script-src",
|
140
|
+
script_src_attr: "script-src-attr",
|
141
|
+
script_src_elem: "script-src-elem",
|
140
142
|
style_src: "style-src",
|
143
|
+
style_src_attr: "style-src-attr",
|
144
|
+
style_src_elem: "style-src-elem",
|
141
145
|
worker_src: "worker-src"
|
142
146
|
}.freeze
|
143
147
|
|
@@ -0,0 +1,168 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/object/deep_dup"
|
4
|
+
|
5
|
+
module ActionDispatch #:nodoc:
|
6
|
+
class FeaturePolicy
|
7
|
+
class Middleware
|
8
|
+
CONTENT_TYPE = "Content-Type"
|
9
|
+
POLICY = "Feature-Policy"
|
10
|
+
|
11
|
+
def initialize(app)
|
12
|
+
@app = app
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
request = ActionDispatch::Request.new(env)
|
17
|
+
_, headers, _ = response = @app.call(env)
|
18
|
+
|
19
|
+
return response unless html_response?(headers)
|
20
|
+
return response if policy_present?(headers)
|
21
|
+
|
22
|
+
if policy = request.feature_policy
|
23
|
+
headers[POLICY] = policy.build(request.controller_instance)
|
24
|
+
end
|
25
|
+
|
26
|
+
if policy_empty?(policy)
|
27
|
+
headers.delete(POLICY)
|
28
|
+
end
|
29
|
+
|
30
|
+
response
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def html_response?(headers)
|
35
|
+
if content_type = headers[CONTENT_TYPE]
|
36
|
+
/html/.match?(content_type)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def policy_present?(headers)
|
41
|
+
headers[POLICY]
|
42
|
+
end
|
43
|
+
|
44
|
+
def policy_empty?(policy)
|
45
|
+
policy&.directives&.empty?
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
module Request
|
50
|
+
POLICY = "action_dispatch.feature_policy"
|
51
|
+
|
52
|
+
def feature_policy
|
53
|
+
get_header(POLICY)
|
54
|
+
end
|
55
|
+
|
56
|
+
def feature_policy=(policy)
|
57
|
+
set_header(POLICY, policy)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
MAPPINGS = {
|
62
|
+
self: "'self'",
|
63
|
+
none: "'none'",
|
64
|
+
}.freeze
|
65
|
+
|
66
|
+
# List of available features can be found at
|
67
|
+
# https://github.com/WICG/feature-policy/blob/master/features.md#policy-controlled-features
|
68
|
+
DIRECTIVES = {
|
69
|
+
accelerometer: "accelerometer",
|
70
|
+
ambient_light_sensor: "ambient-light-sensor",
|
71
|
+
autoplay: "autoplay",
|
72
|
+
camera: "camera",
|
73
|
+
encrypted_media: "encrypted-media",
|
74
|
+
fullscreen: "fullscreen",
|
75
|
+
geolocation: "geolocation",
|
76
|
+
gyroscope: "gyroscope",
|
77
|
+
magnetometer: "magnetometer",
|
78
|
+
microphone: "microphone",
|
79
|
+
midi: "midi",
|
80
|
+
payment: "payment",
|
81
|
+
picture_in_picture: "picture-in-picture",
|
82
|
+
speaker: "speaker",
|
83
|
+
usb: "usb",
|
84
|
+
vibrate: "vibrate",
|
85
|
+
vr: "vr",
|
86
|
+
}.freeze
|
87
|
+
|
88
|
+
private_constant :MAPPINGS, :DIRECTIVES
|
89
|
+
|
90
|
+
attr_reader :directives
|
91
|
+
|
92
|
+
def initialize
|
93
|
+
@directives = {}
|
94
|
+
yield self if block_given?
|
95
|
+
end
|
96
|
+
|
97
|
+
def initialize_copy(other)
|
98
|
+
@directives = other.directives.deep_dup
|
99
|
+
end
|
100
|
+
|
101
|
+
DIRECTIVES.each do |name, directive|
|
102
|
+
define_method(name) do |*sources|
|
103
|
+
if sources.first
|
104
|
+
@directives[directive] = apply_mappings(sources)
|
105
|
+
else
|
106
|
+
@directives.delete(directive)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def build(context = nil)
|
112
|
+
build_directives(context).compact.join("; ")
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
def apply_mappings(sources)
|
117
|
+
sources.map do |source|
|
118
|
+
case source
|
119
|
+
when Symbol
|
120
|
+
apply_mapping(source)
|
121
|
+
when String, Proc
|
122
|
+
source
|
123
|
+
else
|
124
|
+
raise ArgumentError, "Invalid HTTP feature policy source: #{source.inspect}"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def apply_mapping(source)
|
130
|
+
MAPPINGS.fetch(source) do
|
131
|
+
raise ArgumentError, "Unknown HTTP feature policy source mapping: #{source.inspect}"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def build_directives(context)
|
136
|
+
@directives.map do |directive, sources|
|
137
|
+
if sources.is_a?(Array)
|
138
|
+
"#{directive} #{build_directive(sources, context).join(' ')}"
|
139
|
+
elsif sources
|
140
|
+
directive
|
141
|
+
else
|
142
|
+
nil
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def build_directive(sources, context)
|
148
|
+
sources.map { |source| resolve_source(source, context) }
|
149
|
+
end
|
150
|
+
|
151
|
+
def resolve_source(source, context)
|
152
|
+
case source
|
153
|
+
when String
|
154
|
+
source
|
155
|
+
when Symbol
|
156
|
+
source.to_s
|
157
|
+
when Proc
|
158
|
+
if context.nil?
|
159
|
+
raise RuntimeError, "Missing context for the dynamic feature policy source: #{source.inspect}"
|
160
|
+
else
|
161
|
+
context.instance_exec(&source)
|
162
|
+
end
|
163
|
+
else
|
164
|
+
raise RuntimeError, "Unexpected feature policy source: #{source.inspect}"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -23,7 +23,7 @@ module ActionDispatch
|
|
23
23
|
# change { file: { code: "xxxx"} }
|
24
24
|
#
|
25
25
|
# env["action_dispatch.parameter_filter"] = -> (k, v) do
|
26
|
-
# v.reverse! if k
|
26
|
+
# v.reverse! if k.match?(/secret/i)
|
27
27
|
# end
|
28
28
|
# => reverses the value to all keys matching /secret/i
|
29
29
|
module FilterParameters
|
@@ -121,8 +121,9 @@ module ActionDispatch
|
|
121
121
|
def env_name(key)
|
122
122
|
key = key.to_s
|
123
123
|
if HTTP_HEADER.match?(key)
|
124
|
-
key = key.upcase
|
125
|
-
key
|
124
|
+
key = key.upcase
|
125
|
+
key.tr!("-", "_")
|
126
|
+
key.prepend("HTTP_") unless CGI_VARIABLES.include?(key)
|
126
127
|
end
|
127
128
|
key
|
128
129
|
end
|
@@ -68,13 +68,7 @@ module ActionDispatch
|
|
68
68
|
|
69
69
|
def formats
|
70
70
|
fetch_header("action_dispatch.request.formats") do |k|
|
71
|
-
|
72
|
-
parameters[:format]
|
73
|
-
rescue *RESCUABLE_MIME_FORMAT_ERRORS
|
74
|
-
false
|
75
|
-
end
|
76
|
-
|
77
|
-
v = if params_readable
|
71
|
+
v = if params_readable?
|
78
72
|
Array(Mime[parameters[:format]])
|
79
73
|
elsif use_accept_header && valid_accept_header
|
80
74
|
accepts
|
@@ -159,12 +153,24 @@ module ActionDispatch
|
|
159
153
|
order.include?(Mime::ALL) ? format : nil
|
160
154
|
end
|
161
155
|
|
156
|
+
def should_apply_vary_header?
|
157
|
+
!params_readable? && use_accept_header && valid_accept_header
|
158
|
+
end
|
159
|
+
|
162
160
|
private
|
161
|
+
# We use normal content negotiation unless you include */* in your list,
|
162
|
+
# in which case we assume you're a browser and send HTML.
|
163
163
|
BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/
|
164
164
|
|
165
|
+
def params_readable? # :doc:
|
166
|
+
parameters[:format]
|
167
|
+
rescue *RESCUABLE_MIME_FORMAT_ERRORS
|
168
|
+
false
|
169
|
+
end
|
170
|
+
|
165
171
|
def valid_accept_header # :doc:
|
166
172
|
(xhr? && (accept.present? || content_mime_type)) ||
|
167
|
-
(accept.present? && accept
|
173
|
+
(accept.present? && !accept.match?(BROWSER_LIKE_ACCEPTS))
|
168
174
|
end
|
169
175
|
|
170
176
|
def use_accept_header # :doc:
|