actionpack 5.2.1 → 7.0.2.4
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 +264 -220
- data/MIT-LICENSE +1 -1
- data/README.rdoc +6 -6
- data/lib/abstract_controller/asset_paths.rb +1 -1
- data/lib/abstract_controller/base.rb +24 -4
- data/lib/abstract_controller/caching/fragments.rb +8 -24
- data/lib/abstract_controller/caching.rb +2 -2
- data/lib/abstract_controller/callbacks.rb +34 -8
- data/lib/abstract_controller/collector.rb +5 -4
- data/lib/abstract_controller/error.rb +1 -1
- data/lib/abstract_controller/helpers.rb +107 -90
- data/lib/abstract_controller/logger.rb +1 -1
- data/lib/abstract_controller/railties/routes_helpers.rb +19 -1
- data/lib/abstract_controller/rendering.rb +9 -9
- data/lib/abstract_controller/translation.rb +12 -5
- data/lib/abstract_controller/url_for.rb +4 -6
- data/lib/abstract_controller.rb +2 -0
- data/lib/action_controller/api.rb +5 -4
- data/lib/action_controller/base.rb +6 -9
- data/lib/action_controller/caching.rb +1 -3
- data/lib/action_controller/log_subscriber.rb +13 -9
- data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
- data/lib/action_controller/metal/conditional_get.rb +57 -6
- data/lib/action_controller/metal/content_security_policy.rb +2 -3
- data/lib/action_controller/metal/cookies.rb +4 -2
- data/lib/action_controller/metal/data_streaming.rb +9 -18
- data/lib/action_controller/metal/default_headers.rb +17 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +4 -6
- data/lib/action_controller/metal/exceptions.rb +55 -12
- data/lib/action_controller/metal/flash.rb +10 -6
- data/lib/action_controller/metal/head.rb +7 -4
- data/lib/action_controller/metal/helpers.rb +15 -6
- data/lib/action_controller/metal/http_authentication.rb +41 -39
- data/lib/action_controller/metal/implicit_render.rb +5 -15
- data/lib/action_controller/metal/instrumentation.rb +59 -55
- data/lib/action_controller/metal/live.rb +80 -33
- data/lib/action_controller/metal/logging.rb +20 -0
- data/lib/action_controller/metal/mime_responds.rb +22 -7
- data/lib/action_controller/metal/parameter_encoding.rb +35 -4
- data/lib/action_controller/metal/params_wrapper.rb +50 -31
- data/lib/action_controller/metal/permissions_policy.rb +46 -0
- data/lib/action_controller/metal/redirecting.rb +93 -23
- data/lib/action_controller/metal/renderers.rb +4 -4
- data/lib/action_controller/metal/rendering.rb +14 -9
- data/lib/action_controller/metal/request_forgery_protection.rb +160 -58
- data/lib/action_controller/metal/rescue.rb +2 -2
- data/lib/action_controller/metal/streaming.rb +1 -4
- data/lib/action_controller/metal/strong_parameters.rb +236 -88
- data/lib/action_controller/metal/testing.rb +9 -2
- data/lib/action_controller/metal/url_for.rb +1 -1
- data/lib/action_controller/metal.rb +16 -17
- data/lib/action_controller/railtie.rb +49 -6
- data/lib/action_controller/railties/helpers.rb +1 -1
- data/lib/action_controller/renderer.rb +37 -13
- data/lib/action_controller/template_assertions.rb +1 -1
- data/lib/action_controller/test_case.rb +98 -68
- data/lib/action_controller.rb +4 -5
- data/lib/action_dispatch/http/cache.rb +45 -32
- data/lib/action_dispatch/http/content_disposition.rb +45 -0
- data/lib/action_dispatch/http/content_security_policy.rb +69 -56
- data/lib/action_dispatch/http/filter_parameters.rb +14 -8
- data/lib/action_dispatch/http/filter_redirect.rb +2 -3
- data/lib/action_dispatch/http/headers.rb +4 -4
- data/lib/action_dispatch/http/mime_negotiation.rb +44 -16
- data/lib/action_dispatch/http/mime_type.rb +47 -30
- data/lib/action_dispatch/http/parameters.rb +18 -27
- data/lib/action_dispatch/http/permissions_policy.rb +173 -0
- data/lib/action_dispatch/http/request.rb +49 -35
- data/lib/action_dispatch/http/response.rb +34 -26
- data/lib/action_dispatch/http/upload.rb +9 -1
- data/lib/action_dispatch/http/url.rb +86 -94
- data/lib/action_dispatch/journey/formatter.rb +55 -31
- data/lib/action_dispatch/journey/gtg/builder.rb +30 -46
- data/lib/action_dispatch/journey/gtg/simulator.rb +15 -8
- data/lib/action_dispatch/journey/gtg/transition_table.rb +78 -21
- data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
- data/lib/action_dispatch/journey/nodes/node.rb +83 -16
- 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 +42 -34
- data/lib/action_dispatch/journey/route.rb +14 -31
- data/lib/action_dispatch/journey/router/utils.rb +16 -14
- data/lib/action_dispatch/journey/router.rb +27 -35
- data/lib/action_dispatch/journey/routes.rb +3 -5
- data/lib/action_dispatch/journey/scanner.rb +10 -4
- data/lib/action_dispatch/journey/visitors.rb +1 -4
- data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
- data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
- data/lib/action_dispatch/journey.rb +0 -2
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +45 -0
- data/lib/action_dispatch/middleware/callbacks.rb +2 -4
- data/lib/action_dispatch/middleware/cookies.rb +136 -113
- data/lib/action_dispatch/middleware/debug_exceptions.rb +47 -68
- data/lib/action_dispatch/middleware/debug_locks.rb +8 -8
- data/lib/action_dispatch/middleware/debug_view.rb +66 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +79 -30
- data/lib/action_dispatch/middleware/executor.rb +4 -1
- data/lib/action_dispatch/middleware/flash.rb +10 -12
- data/lib/action_dispatch/middleware/host_authorization.rb +159 -0
- data/lib/action_dispatch/middleware/public_exceptions.rb +6 -3
- data/lib/action_dispatch/middleware/remote_ip.rb +30 -20
- data/lib/action_dispatch/middleware/request_id.rb +5 -6
- data/lib/action_dispatch/middleware/server_timing.rb +33 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +16 -3
- data/lib/action_dispatch/middleware/session/cache_store.rb +11 -6
- data/lib/action_dispatch/middleware/session/cookie_store.rb +24 -19
- data/lib/action_dispatch/middleware/show_exceptions.rb +20 -11
- data/lib/action_dispatch/middleware/ssl.rb +20 -15
- data/lib/action_dispatch/middleware/stack.rb +79 -7
- data/lib/action_dispatch/middleware/static.rb +150 -94
- data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
- data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +6 -11
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +4 -2
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +46 -36
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +8 -0
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +7 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +25 -6
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +9 -6
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +4 -1
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +121 -15
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +5 -5
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +5 -5
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +16 -2
- data/lib/action_dispatch/railtie.rb +16 -4
- data/lib/action_dispatch/request/session.rb +59 -22
- data/lib/action_dispatch/request/utils.rb +28 -2
- data/lib/action_dispatch/routing/inspector.rb +102 -54
- data/lib/action_dispatch/routing/mapper.rb +184 -156
- data/lib/action_dispatch/routing/polymorphic_routes.rb +21 -19
- data/lib/action_dispatch/routing/redirection.rb +4 -6
- data/lib/action_dispatch/routing/route_set.rb +83 -73
- data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
- data/lib/action_dispatch/routing/url_for.rb +2 -3
- data/lib/action_dispatch/routing.rb +23 -22
- data/lib/action_dispatch/system_test_case.rb +65 -16
- data/lib/action_dispatch/system_testing/browser.rb +43 -16
- data/lib/action_dispatch/system_testing/driver.rb +42 -10
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +58 -12
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +3 -10
- data/lib/action_dispatch/testing/assertion_response.rb +0 -1
- data/lib/action_dispatch/testing/assertions/response.rb +4 -7
- data/lib/action_dispatch/testing/assertions/routing.rb +20 -8
- data/lib/action_dispatch/testing/assertions.rb +3 -6
- data/lib/action_dispatch/testing/integration.rb +61 -30
- data/lib/action_dispatch/testing/request_encoder.rb +2 -2
- data/lib/action_dispatch/testing/test_process.rb +8 -6
- data/lib/action_dispatch/testing/test_request.rb +3 -3
- data/lib/action_dispatch/testing/test_response.rb +4 -32
- data/lib/action_dispatch.rb +15 -7
- data/lib/action_pack/gem_version.rb +4 -4
- data/lib/action_pack.rb +1 -1
- metadata +44 -25
- data/lib/action_controller/metal/force_ssl.rb +0 -99
- data/lib/action_dispatch/http/parameter_filter.rb +0 -86
- data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
- data/lib/action_dispatch/journey/nfa/simulator.rb +0 -49
- data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -120
- data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +0 -26
@@ -4,8 +4,8 @@ module ActionDispatch
|
|
4
4
|
module Http
|
5
5
|
module Cache
|
6
6
|
module Request
|
7
|
-
HTTP_IF_MODIFIED_SINCE = "HTTP_IF_MODIFIED_SINCE"
|
8
|
-
HTTP_IF_NONE_MATCH = "HTTP_IF_NONE_MATCH"
|
7
|
+
HTTP_IF_MODIFIED_SINCE = "HTTP_IF_MODIFIED_SINCE"
|
8
|
+
HTTP_IF_NONE_MATCH = "HTTP_IF_NONE_MATCH"
|
9
9
|
|
10
10
|
def if_modified_since
|
11
11
|
if since = get_header(HTTP_IF_MODIFIED_SINCE)
|
@@ -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/)
|
@@ -123,10 +123,9 @@ module ActionDispatch
|
|
123
123
|
end
|
124
124
|
|
125
125
|
private
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
SPECIAL_KEYS = Set.new(%w[extras no-cache max-age public private must-revalidate])
|
126
|
+
DATE = "Date"
|
127
|
+
LAST_MODIFIED = "Last-Modified"
|
128
|
+
SPECIAL_KEYS = Set.new(%w[extras no-store no-cache max-age public private must-revalidate])
|
130
129
|
|
131
130
|
def generate_weak_etag(validators)
|
132
131
|
"W/#{generate_strong_etag(validators)}"
|
@@ -151,8 +150,8 @@ module ActionDispatch
|
|
151
150
|
directive, argument = segment.split("=", 2)
|
152
151
|
|
153
152
|
if SPECIAL_KEYS.include? directive
|
154
|
-
|
155
|
-
cache_control[
|
153
|
+
directive.tr!("-", "_")
|
154
|
+
cache_control[directive.to_sym] = argument || true
|
156
155
|
else
|
157
156
|
cache_control[:extras] ||= []
|
158
157
|
cache_control[:extras] << segment
|
@@ -166,11 +165,12 @@ module ActionDispatch
|
|
166
165
|
@cache_control = cache_control_headers
|
167
166
|
end
|
168
167
|
|
169
|
-
DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate"
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
168
|
+
DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate"
|
169
|
+
NO_STORE = "no-store"
|
170
|
+
NO_CACHE = "no-cache"
|
171
|
+
PUBLIC = "public"
|
172
|
+
PRIVATE = "private"
|
173
|
+
MUST_REVALIDATE = "must-revalidate"
|
174
174
|
|
175
175
|
def handle_conditional_get!
|
176
176
|
# Normally default cache control setting is handled by ETag
|
@@ -183,36 +183,49 @@ module ActionDispatch
|
|
183
183
|
end
|
184
184
|
|
185
185
|
def merge_and_normalize_cache_control!(cache_control)
|
186
|
-
control =
|
187
|
-
|
188
|
-
if
|
189
|
-
|
190
|
-
|
191
|
-
|
186
|
+
control = cache_control_headers
|
187
|
+
|
188
|
+
return if control.empty? && cache_control.empty? # Let middleware handle default behavior
|
189
|
+
|
190
|
+
if cache_control.any?
|
191
|
+
# Any caching directive coming from a controller overrides
|
192
|
+
# no-cache/no-store in the default Cache-Control header.
|
193
|
+
control.delete(:no_cache)
|
194
|
+
control.delete(:no_store)
|
195
|
+
|
196
|
+
if extras = control.delete(:extras)
|
197
|
+
cache_control[:extras] ||= []
|
198
|
+
cache_control[:extras] += extras
|
199
|
+
cache_control[:extras].uniq!
|
200
|
+
end
|
201
|
+
|
202
|
+
control.merge! cache_control
|
192
203
|
end
|
193
204
|
|
194
|
-
|
195
|
-
control.merge! cache_control
|
205
|
+
options = []
|
196
206
|
|
197
|
-
if control
|
198
|
-
|
207
|
+
if control[:no_store]
|
208
|
+
options << PRIVATE if control[:private]
|
209
|
+
options << NO_STORE
|
199
210
|
elsif control[:no_cache]
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
end
|
211
|
+
options << PUBLIC if control[:public]
|
212
|
+
options << NO_CACHE
|
213
|
+
options.concat(control[:extras]) if control[:extras]
|
204
214
|
else
|
205
|
-
extras
|
215
|
+
extras = control[:extras]
|
206
216
|
max_age = control[:max_age]
|
217
|
+
stale_while_revalidate = control[:stale_while_revalidate]
|
218
|
+
stale_if_error = control[:stale_if_error]
|
207
219
|
|
208
|
-
options = []
|
209
220
|
options << "max-age=#{max_age.to_i}" if max_age
|
210
221
|
options << (control[:public] ? PUBLIC : PRIVATE)
|
211
222
|
options << MUST_REVALIDATE if control[:must_revalidate]
|
223
|
+
options << "stale-while-revalidate=#{stale_while_revalidate.to_i}" if stale_while_revalidate
|
224
|
+
options << "stale-if-error=#{stale_if_error.to_i}" if stale_if_error
|
212
225
|
options.concat(extras) if extras
|
213
|
-
|
214
|
-
self._cache_control = options.join(", ")
|
215
226
|
end
|
227
|
+
|
228
|
+
self._cache_control = options.join(", ")
|
216
229
|
end
|
217
230
|
end
|
218
231
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionDispatch
|
4
|
+
module Http
|
5
|
+
class ContentDisposition # :nodoc:
|
6
|
+
def self.format(disposition:, filename:)
|
7
|
+
new(disposition: disposition, filename: filename).to_s
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :disposition, :filename
|
11
|
+
|
12
|
+
def initialize(disposition:, filename:)
|
13
|
+
@disposition = disposition
|
14
|
+
@filename = filename
|
15
|
+
end
|
16
|
+
|
17
|
+
TRADITIONAL_ESCAPED_CHAR = /[^ A-Za-z0-9!\#$+.^_`|~-]/
|
18
|
+
|
19
|
+
def ascii_filename
|
20
|
+
'filename="' + percent_escape(I18n.transliterate(filename), TRADITIONAL_ESCAPED_CHAR) + '"'
|
21
|
+
end
|
22
|
+
|
23
|
+
RFC_5987_ESCAPED_CHAR = /[^A-Za-z0-9!\#$&+.^_`|~-]/
|
24
|
+
|
25
|
+
def utf8_filename
|
26
|
+
"filename*=UTF-8''" + percent_escape(filename, RFC_5987_ESCAPED_CHAR)
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
if filename
|
31
|
+
"#{disposition}; #{ascii_filename}; #{utf8_filename}"
|
32
|
+
else
|
33
|
+
"#{disposition}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
def percent_escape(string, pattern)
|
39
|
+
string.gsub(pattern) do |char|
|
40
|
+
char.bytes.map { |byte| "%%%02X" % byte }.join
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -2,12 +2,12 @@
|
|
2
2
|
|
3
3
|
require "active_support/core_ext/object/deep_dup"
|
4
4
|
|
5
|
-
module ActionDispatch
|
5
|
+
module ActionDispatch # :nodoc:
|
6
6
|
class ContentSecurityPolicy
|
7
7
|
class Middleware
|
8
|
-
CONTENT_TYPE = "Content-Type"
|
9
|
-
POLICY = "Content-Security-Policy"
|
10
|
-
POLICY_REPORT_ONLY = "Content-Security-Policy-Report-Only"
|
8
|
+
CONTENT_TYPE = "Content-Type"
|
9
|
+
POLICY = "Content-Security-Policy"
|
10
|
+
POLICY_REPORT_ONLY = "Content-Security-Policy-Report-Only"
|
11
11
|
|
12
12
|
def initialize(app)
|
13
13
|
@app = app
|
@@ -17,25 +17,19 @@ module ActionDispatch #:nodoc:
|
|
17
17
|
request = ActionDispatch::Request.new env
|
18
18
|
_, headers, _ = response = @app.call(env)
|
19
19
|
|
20
|
-
return response unless html_response?(headers)
|
21
20
|
return response if policy_present?(headers)
|
22
21
|
|
23
22
|
if policy = request.content_security_policy
|
24
23
|
nonce = request.content_security_policy_nonce
|
25
|
-
|
24
|
+
nonce_directives = request.content_security_policy_nonce_directives
|
25
|
+
context = request.controller_instance || request
|
26
|
+
headers[header_name(request)] = policy.build(context, nonce, nonce_directives)
|
26
27
|
end
|
27
28
|
|
28
29
|
response
|
29
30
|
end
|
30
31
|
|
31
32
|
private
|
32
|
-
|
33
|
-
def html_response?(headers)
|
34
|
-
if content_type = headers[CONTENT_TYPE]
|
35
|
-
content_type =~ /html/
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
33
|
def header_name(request)
|
40
34
|
if request.content_security_policy_report_only
|
41
35
|
POLICY_REPORT_ONLY
|
@@ -50,10 +44,11 @@ module ActionDispatch #:nodoc:
|
|
50
44
|
end
|
51
45
|
|
52
46
|
module Request
|
53
|
-
POLICY = "action_dispatch.content_security_policy"
|
54
|
-
POLICY_REPORT_ONLY = "action_dispatch.content_security_policy_report_only"
|
55
|
-
NONCE_GENERATOR = "action_dispatch.content_security_policy_nonce_generator"
|
56
|
-
NONCE = "action_dispatch.content_security_policy_nonce"
|
47
|
+
POLICY = "action_dispatch.content_security_policy"
|
48
|
+
POLICY_REPORT_ONLY = "action_dispatch.content_security_policy_report_only"
|
49
|
+
NONCE_GENERATOR = "action_dispatch.content_security_policy_nonce_generator"
|
50
|
+
NONCE = "action_dispatch.content_security_policy_nonce"
|
51
|
+
NONCE_DIRECTIVES = "action_dispatch.content_security_policy_nonce_directives"
|
57
52
|
|
58
53
|
def content_security_policy
|
59
54
|
get_header(POLICY)
|
@@ -79,6 +74,14 @@ module ActionDispatch #:nodoc:
|
|
79
74
|
set_header(NONCE_GENERATOR, generator)
|
80
75
|
end
|
81
76
|
|
77
|
+
def content_security_policy_nonce_directives
|
78
|
+
get_header(NONCE_DIRECTIVES)
|
79
|
+
end
|
80
|
+
|
81
|
+
def content_security_policy_nonce_directives=(generator)
|
82
|
+
set_header(NONCE_DIRECTIVES, generator)
|
83
|
+
end
|
84
|
+
|
82
85
|
def content_security_policy_nonce
|
83
86
|
if content_security_policy_nonce_generator
|
84
87
|
if nonce = get_header(NONCE)
|
@@ -90,50 +93,58 @@ module ActionDispatch #:nodoc:
|
|
90
93
|
end
|
91
94
|
|
92
95
|
private
|
93
|
-
|
94
96
|
def generate_content_security_policy_nonce
|
95
97
|
content_security_policy_nonce_generator.call(self)
|
96
98
|
end
|
97
99
|
end
|
98
100
|
|
99
101
|
MAPPINGS = {
|
100
|
-
self:
|
101
|
-
unsafe_eval:
|
102
|
-
unsafe_inline:
|
103
|
-
none:
|
104
|
-
http:
|
105
|
-
https:
|
106
|
-
data:
|
107
|
-
mediastream:
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
102
|
+
self: "'self'",
|
103
|
+
unsafe_eval: "'unsafe-eval'",
|
104
|
+
unsafe_inline: "'unsafe-inline'",
|
105
|
+
none: "'none'",
|
106
|
+
http: "http:",
|
107
|
+
https: "https:",
|
108
|
+
data: "data:",
|
109
|
+
mediastream: "mediastream:",
|
110
|
+
allow_duplicates: "'allow-duplicates'",
|
111
|
+
blob: "blob:",
|
112
|
+
filesystem: "filesystem:",
|
113
|
+
report_sample: "'report-sample'",
|
114
|
+
script: "'script'",
|
115
|
+
strict_dynamic: "'strict-dynamic'",
|
116
|
+
ws: "ws:",
|
117
|
+
wss: "wss:"
|
114
118
|
}.freeze
|
115
119
|
|
116
120
|
DIRECTIVES = {
|
117
|
-
base_uri:
|
118
|
-
child_src:
|
119
|
-
connect_src:
|
120
|
-
default_src:
|
121
|
-
font_src:
|
122
|
-
form_action:
|
123
|
-
frame_ancestors:
|
124
|
-
frame_src:
|
125
|
-
img_src:
|
126
|
-
manifest_src:
|
127
|
-
media_src:
|
128
|
-
object_src:
|
129
|
-
|
130
|
-
|
131
|
-
|
121
|
+
base_uri: "base-uri",
|
122
|
+
child_src: "child-src",
|
123
|
+
connect_src: "connect-src",
|
124
|
+
default_src: "default-src",
|
125
|
+
font_src: "font-src",
|
126
|
+
form_action: "form-action",
|
127
|
+
frame_ancestors: "frame-ancestors",
|
128
|
+
frame_src: "frame-src",
|
129
|
+
img_src: "img-src",
|
130
|
+
manifest_src: "manifest-src",
|
131
|
+
media_src: "media-src",
|
132
|
+
object_src: "object-src",
|
133
|
+
prefetch_src: "prefetch-src",
|
134
|
+
require_trusted_types_for: "require-trusted-types-for",
|
135
|
+
script_src: "script-src",
|
136
|
+
script_src_attr: "script-src-attr",
|
137
|
+
script_src_elem: "script-src-elem",
|
138
|
+
style_src: "style-src",
|
139
|
+
style_src_attr: "style-src-attr",
|
140
|
+
style_src_elem: "style-src-elem",
|
141
|
+
trusted_types: "trusted-types",
|
142
|
+
worker_src: "worker-src"
|
132
143
|
}.freeze
|
133
144
|
|
134
|
-
|
145
|
+
DEFAULT_NONCE_DIRECTIVES = %w[script-src style-src].freeze
|
135
146
|
|
136
|
-
private_constant :MAPPINGS, :DIRECTIVES, :
|
147
|
+
private_constant :MAPPINGS, :DIRECTIVES, :DEFAULT_NONCE_DIRECTIVES
|
137
148
|
|
138
149
|
attr_reader :directives
|
139
150
|
|
@@ -202,8 +213,9 @@ module ActionDispatch #:nodoc:
|
|
202
213
|
end
|
203
214
|
end
|
204
215
|
|
205
|
-
def build(context = nil, nonce = nil)
|
206
|
-
|
216
|
+
def build(context = nil, nonce = nil, nonce_directives = nil)
|
217
|
+
nonce_directives = DEFAULT_NONCE_DIRECTIVES if nonce_directives.nil?
|
218
|
+
build_directives(context, nonce, nonce_directives).compact.join("; ")
|
207
219
|
end
|
208
220
|
|
209
221
|
private
|
@@ -226,10 +238,10 @@ module ActionDispatch #:nodoc:
|
|
226
238
|
end
|
227
239
|
end
|
228
240
|
|
229
|
-
def build_directives(context, nonce)
|
241
|
+
def build_directives(context, nonce, nonce_directives)
|
230
242
|
@directives.map do |directive, sources|
|
231
243
|
if sources.is_a?(Array)
|
232
|
-
if nonce && nonce_directive?(directive)
|
244
|
+
if nonce && nonce_directive?(directive, nonce_directives)
|
233
245
|
"#{directive} #{build_directive(sources, context).join(' ')} 'nonce-#{nonce}'"
|
234
246
|
else
|
235
247
|
"#{directive} #{build_directive(sources, context).join(' ')}"
|
@@ -256,15 +268,16 @@ module ActionDispatch #:nodoc:
|
|
256
268
|
if context.nil?
|
257
269
|
raise RuntimeError, "Missing context for the dynamic content security policy source: #{source.inspect}"
|
258
270
|
else
|
259
|
-
context.instance_exec(&source)
|
271
|
+
resolved = context.instance_exec(&source)
|
272
|
+
resolved.is_a?(Symbol) ? apply_mapping(resolved) : resolved
|
260
273
|
end
|
261
274
|
else
|
262
275
|
raise RuntimeError, "Unexpected content security policy source: #{source.inspect}"
|
263
276
|
end
|
264
277
|
end
|
265
278
|
|
266
|
-
def nonce_directive?(directive)
|
267
|
-
|
279
|
+
def nonce_directive?(directive, nonce_directives)
|
280
|
+
nonce_directives.include?(directive)
|
268
281
|
end
|
269
282
|
end
|
270
283
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "active_support/parameter_filter"
|
4
4
|
|
5
5
|
module ActionDispatch
|
6
6
|
module Http
|
@@ -9,8 +9,8 @@ module ActionDispatch
|
|
9
9
|
# sub-hashes of the params hash to filter. Filtering only certain sub-keys
|
10
10
|
# from a hash is possible by using the dot notation: 'credit_card.number'.
|
11
11
|
# If a block is given, each key and value of the params hash and all
|
12
|
-
# sub-hashes
|
13
|
-
# String#replace or similar
|
12
|
+
# sub-hashes are passed to it, where the value or the key can be replaced using
|
13
|
+
# String#replace or similar methods.
|
14
14
|
#
|
15
15
|
# env["action_dispatch.parameter_filter"] = [:password]
|
16
16
|
# => replaces the value to all keys matching /password/i with "[FILTERED]"
|
@@ -18,18 +18,23 @@ module ActionDispatch
|
|
18
18
|
# env["action_dispatch.parameter_filter"] = [:foo, "bar"]
|
19
19
|
# => replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
|
20
20
|
#
|
21
|
+
# env["action_dispatch.parameter_filter"] = [ /\Apin\z/i, /\Apin_/i ]
|
22
|
+
# => replaces the value for the exact (case-insensitive) key 'pin' and all
|
23
|
+
# (case-insensitive) keys beginning with 'pin_', with "[FILTERED]"
|
24
|
+
# Does not match keys with 'pin' as a substring, such as 'shipping_id'.
|
25
|
+
#
|
21
26
|
# env["action_dispatch.parameter_filter"] = [ "credit_card.code" ]
|
22
27
|
# => replaces { credit_card: {code: "xxxx"} } with "[FILTERED]", does not
|
23
28
|
# change { file: { code: "xxxx"} }
|
24
29
|
#
|
25
30
|
# env["action_dispatch.parameter_filter"] = -> (k, v) do
|
26
|
-
# v.reverse! if k
|
31
|
+
# v.reverse! if k.match?(/secret/i)
|
27
32
|
# end
|
28
33
|
# => reverses the value to all keys matching /secret/i
|
29
34
|
module FilterParameters
|
30
35
|
ENV_MATCH = [/RAW_POST_DATA/, "rack.request.form_vars"] # :nodoc:
|
31
|
-
NULL_PARAM_FILTER = ParameterFilter.new # :nodoc:
|
32
|
-
NULL_ENV_FILTER = ParameterFilter.new ENV_MATCH # :nodoc:
|
36
|
+
NULL_PARAM_FILTER = ActiveSupport::ParameterFilter.new # :nodoc:
|
37
|
+
NULL_ENV_FILTER = ActiveSupport::ParameterFilter.new ENV_MATCH # :nodoc:
|
33
38
|
|
34
39
|
def initialize
|
35
40
|
super
|
@@ -41,6 +46,8 @@ module ActionDispatch
|
|
41
46
|
# Returns a hash of parameters with all sensitive data replaced.
|
42
47
|
def filtered_parameters
|
43
48
|
@filtered_parameters ||= parameter_filter.filter(parameters)
|
49
|
+
rescue ActionDispatch::Http::Parameters::ParseError
|
50
|
+
@filtered_parameters = {}
|
44
51
|
end
|
45
52
|
|
46
53
|
# Returns a hash of request.env with all sensitive data replaced.
|
@@ -54,7 +61,6 @@ module ActionDispatch
|
|
54
61
|
end
|
55
62
|
|
56
63
|
private
|
57
|
-
|
58
64
|
def parameter_filter # :doc:
|
59
65
|
parameter_filter_for fetch_header("action_dispatch.parameter_filter") {
|
60
66
|
return NULL_PARAM_FILTER
|
@@ -69,7 +75,7 @@ module ActionDispatch
|
|
69
75
|
end
|
70
76
|
|
71
77
|
def parameter_filter_for(filters) # :doc:
|
72
|
-
ParameterFilter.new(filters)
|
78
|
+
ActiveSupport::ParameterFilter.new(filters)
|
73
79
|
end
|
74
80
|
|
75
81
|
KV_RE = "[^&;=]+"
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module ActionDispatch
|
4
4
|
module Http
|
5
5
|
module FilterRedirect
|
6
|
-
FILTERED = "[FILTERED]"
|
6
|
+
FILTERED = "[FILTERED]" # :nodoc:
|
7
7
|
|
8
8
|
def filtered_location # :nodoc:
|
9
9
|
if location_filter_match?
|
@@ -14,7 +14,6 @@ module ActionDispatch
|
|
14
14
|
end
|
15
15
|
|
16
16
|
private
|
17
|
-
|
18
17
|
def location_filters
|
19
18
|
if request
|
20
19
|
request.get_header("action_dispatch.redirect_filter") || []
|
@@ -28,7 +27,7 @@ module ActionDispatch
|
|
28
27
|
if String === filter
|
29
28
|
location.include?(filter)
|
30
29
|
elsif Regexp === filter
|
31
|
-
location
|
30
|
+
location.match?(filter)
|
32
31
|
end
|
33
32
|
end
|
34
33
|
end
|
@@ -116,14 +116,14 @@ module ActionDispatch
|
|
116
116
|
def env; @req.env.dup; end
|
117
117
|
|
118
118
|
private
|
119
|
-
|
120
119
|
# Converts an HTTP header name to an environment variable name if it is
|
121
120
|
# not contained within the headers hash.
|
122
121
|
def env_name(key)
|
123
122
|
key = key.to_s
|
124
|
-
if key
|
125
|
-
key = key.upcase
|
126
|
-
key
|
123
|
+
if HTTP_HEADER.match?(key)
|
124
|
+
key = key.upcase
|
125
|
+
key.tr!("-", "_")
|
126
|
+
key.prepend("HTTP_") unless CGI_VARIABLES.include?(key)
|
127
127
|
end
|
128
128
|
key
|
129
129
|
end
|
@@ -7,24 +7,43 @@ module ActionDispatch
|
|
7
7
|
module MimeNegotiation
|
8
8
|
extend ActiveSupport::Concern
|
9
9
|
|
10
|
+
class InvalidType < ::Mime::Type::InvalidMimeType; end
|
11
|
+
|
12
|
+
RESCUABLE_MIME_FORMAT_ERRORS = [
|
13
|
+
ActionController::BadRequest,
|
14
|
+
ActionDispatch::Http::Parameters::ParseError,
|
15
|
+
]
|
16
|
+
|
10
17
|
included do
|
11
18
|
mattr_accessor :ignore_accept_header, default: false
|
19
|
+
cattr_accessor :return_only_media_type_on_content_type, default: false
|
12
20
|
end
|
13
21
|
|
14
22
|
# The MIME type of the HTTP request, such as Mime[:xml].
|
15
23
|
def content_mime_type
|
16
24
|
fetch_header("action_dispatch.request.content_type") do |k|
|
17
|
-
v = if get_header("CONTENT_TYPE") =~ /^([
|
25
|
+
v = if get_header("CONTENT_TYPE") =~ /^([^,;]*)/
|
18
26
|
Mime::Type.lookup($1.strip.downcase)
|
19
27
|
else
|
20
28
|
nil
|
21
29
|
end
|
22
30
|
set_header k, v
|
31
|
+
rescue ::Mime::Type::InvalidMimeType => e
|
32
|
+
raise InvalidType, e.message
|
23
33
|
end
|
24
34
|
end
|
25
35
|
|
26
36
|
def content_type
|
27
|
-
|
37
|
+
if self.class.return_only_media_type_on_content_type
|
38
|
+
ActiveSupport::Deprecation.warn(
|
39
|
+
"Rails 7.1 will return Content-Type header without modification." \
|
40
|
+
" If you want just the MIME type, please use `#media_type` instead."
|
41
|
+
)
|
42
|
+
|
43
|
+
content_mime_type&.to_s
|
44
|
+
else
|
45
|
+
super
|
46
|
+
end
|
28
47
|
end
|
29
48
|
|
30
49
|
def has_content_type? # :nodoc:
|
@@ -42,6 +61,8 @@ module ActionDispatch
|
|
42
61
|
Mime::Type.parse(header)
|
43
62
|
end
|
44
63
|
set_header k, v
|
64
|
+
rescue ::Mime::Type::InvalidMimeType => e
|
65
|
+
raise InvalidType, e.message
|
45
66
|
end
|
46
67
|
end
|
47
68
|
|
@@ -57,13 +78,7 @@ module ActionDispatch
|
|
57
78
|
|
58
79
|
def formats
|
59
80
|
fetch_header("action_dispatch.request.formats") do |k|
|
60
|
-
|
61
|
-
parameters[:format]
|
62
|
-
rescue ActionController::BadRequest
|
63
|
-
false
|
64
|
-
end
|
65
|
-
|
66
|
-
v = if params_readable
|
81
|
+
v = if params_readable?
|
67
82
|
Array(Mime[parameters[:format]])
|
68
83
|
elsif use_accept_header && valid_accept_header
|
69
84
|
accepts
|
@@ -74,6 +89,11 @@ module ActionDispatch
|
|
74
89
|
else
|
75
90
|
[Mime[:html]]
|
76
91
|
end
|
92
|
+
|
93
|
+
v = v.select do |format|
|
94
|
+
format.symbol || format.ref == "*/*"
|
95
|
+
end
|
96
|
+
|
77
97
|
set_header k, v
|
78
98
|
end
|
79
99
|
end
|
@@ -82,13 +102,10 @@ module ActionDispatch
|
|
82
102
|
def variant=(variant)
|
83
103
|
variant = Array(variant)
|
84
104
|
|
85
|
-
if variant.all?
|
105
|
+
if variant.all?(Symbol)
|
86
106
|
@variant = ActiveSupport::ArrayInquirer.new(variant)
|
87
107
|
else
|
88
|
-
raise ArgumentError, "request.variant must be set to a Symbol or an Array of Symbols.
|
89
|
-
"For security reasons, never directly set the variant to a user-provided value, " \
|
90
|
-
"like params[:variant].to_sym. Check user-provided value against a whitelist first, " \
|
91
|
-
"then set the variant: request.variant = :tablet if params[:variant] == 'tablet'"
|
108
|
+
raise ArgumentError, "request.variant must be set to a Symbol or an Array of Symbols."
|
92
109
|
end
|
93
110
|
end
|
94
111
|
|
@@ -146,13 +163,24 @@ module ActionDispatch
|
|
146
163
|
order.include?(Mime::ALL) ? format : nil
|
147
164
|
end
|
148
165
|
|
149
|
-
|
166
|
+
def should_apply_vary_header?
|
167
|
+
!params_readable? && use_accept_header && valid_accept_header
|
168
|
+
end
|
150
169
|
|
170
|
+
private
|
171
|
+
# We use normal content negotiation unless you include */* in your list,
|
172
|
+
# in which case we assume you're a browser and send HTML.
|
151
173
|
BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/
|
152
174
|
|
175
|
+
def params_readable? # :doc:
|
176
|
+
parameters[:format]
|
177
|
+
rescue *RESCUABLE_MIME_FORMAT_ERRORS
|
178
|
+
false
|
179
|
+
end
|
180
|
+
|
153
181
|
def valid_accept_header # :doc:
|
154
182
|
(xhr? && (accept.present? || content_mime_type)) ||
|
155
|
-
(accept.present? && accept
|
183
|
+
(accept.present? && !accept.match?(BROWSER_LIKE_ACCEPTS))
|
156
184
|
end
|
157
185
|
|
158
186
|
def use_accept_header # :doc:
|