actionpack 7.0.8.7 → 7.1.5.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +423 -342
- data/MIT-LICENSE +1 -1
- data/README.rdoc +2 -2
- data/lib/abstract_controller/base.rb +20 -11
- data/lib/abstract_controller/caching/fragments.rb +2 -0
- data/lib/abstract_controller/callbacks.rb +31 -6
- data/lib/abstract_controller/deprecator.rb +7 -0
- data/lib/abstract_controller/helpers.rb +61 -18
- data/lib/abstract_controller/railties/routes_helpers.rb +1 -16
- data/lib/abstract_controller/rendering.rb +3 -3
- data/lib/abstract_controller/translation.rb +7 -24
- data/lib/abstract_controller/url_for.rb +2 -0
- data/lib/abstract_controller.rb +6 -0
- data/lib/action_controller/api.rb +5 -3
- data/lib/action_controller/base.rb +3 -17
- data/lib/action_controller/caching.rb +2 -0
- data/lib/action_controller/deprecator.rb +7 -0
- data/lib/action_controller/form_builder.rb +2 -0
- data/lib/action_controller/log_subscriber.rb +16 -4
- data/lib/action_controller/metal/content_security_policy.rb +1 -1
- data/lib/action_controller/metal/data_streaming.rb +2 -0
- data/lib/action_controller/metal/default_headers.rb +2 -0
- data/lib/action_controller/metal/etag_with_flash.rb +2 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +2 -0
- data/lib/action_controller/metal/exceptions.rb +8 -0
- data/lib/action_controller/metal/head.rb +8 -6
- data/lib/action_controller/metal/helpers.rb +3 -14
- data/lib/action_controller/metal/http_authentication.rb +13 -8
- data/lib/action_controller/metal/implicit_render.rb +5 -3
- data/lib/action_controller/metal/instrumentation.rb +8 -1
- data/lib/action_controller/metal/live.rb +24 -0
- data/lib/action_controller/metal/mime_responds.rb +2 -2
- data/lib/action_controller/metal/params_wrapper.rb +4 -2
- data/lib/action_controller/metal/permissions_policy.rb +1 -1
- data/lib/action_controller/metal/redirecting.rb +7 -7
- data/lib/action_controller/metal/renderers.rb +2 -2
- data/lib/action_controller/metal/rendering.rb +0 -7
- data/lib/action_controller/metal/request_forgery_protection.rb +139 -50
- data/lib/action_controller/metal/rescue.rb +2 -0
- data/lib/action_controller/metal/streaming.rb +70 -30
- data/lib/action_controller/metal/strong_parameters.rb +174 -54
- data/lib/action_controller/metal/url_for.rb +7 -0
- data/lib/action_controller/metal.rb +79 -21
- data/lib/action_controller/railtie.rb +22 -9
- data/lib/action_controller/renderer.rb +98 -65
- data/lib/action_controller/test_case.rb +18 -8
- data/lib/action_controller.rb +13 -3
- data/lib/action_dispatch/constants.rb +32 -0
- data/lib/action_dispatch/deprecator.rb +7 -0
- data/lib/action_dispatch/http/cache.rb +1 -3
- data/lib/action_dispatch/http/content_security_policy.rb +9 -8
- data/lib/action_dispatch/http/filter_parameters.rb +11 -5
- data/lib/action_dispatch/http/headers.rb +2 -0
- data/lib/action_dispatch/http/mime_negotiation.rb +22 -22
- data/lib/action_dispatch/http/mime_type.rb +37 -11
- data/lib/action_dispatch/http/mime_types.rb +3 -1
- data/lib/action_dispatch/http/parameters.rb +1 -1
- data/lib/action_dispatch/http/permissions_policy.rb +38 -16
- data/lib/action_dispatch/http/rack_cache.rb +2 -0
- data/lib/action_dispatch/http/request.rb +70 -16
- data/lib/action_dispatch/http/response.rb +80 -59
- data/lib/action_dispatch/http/upload.rb +2 -0
- data/lib/action_dispatch/journey/formatter.rb +8 -2
- data/lib/action_dispatch/journey/path/pattern.rb +14 -14
- data/lib/action_dispatch/journey/route.rb +3 -2
- data/lib/action_dispatch/journey/router.rb +9 -8
- data/lib/action_dispatch/journey/routes.rb +2 -2
- data/lib/action_dispatch/log_subscriber.rb +23 -0
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +5 -6
- data/lib/action_dispatch/middleware/assume_ssl.rb +24 -0
- data/lib/action_dispatch/middleware/callbacks.rb +2 -0
- data/lib/action_dispatch/middleware/cookies.rb +81 -98
- data/lib/action_dispatch/middleware/debug_exceptions.rb +26 -25
- data/lib/action_dispatch/middleware/debug_locks.rb +4 -1
- data/lib/action_dispatch/middleware/debug_view.rb +7 -2
- data/lib/action_dispatch/middleware/exception_wrapper.rb +186 -27
- data/lib/action_dispatch/middleware/executor.rb +7 -1
- data/lib/action_dispatch/middleware/flash.rb +7 -0
- data/lib/action_dispatch/middleware/host_authorization.rb +6 -3
- data/lib/action_dispatch/middleware/public_exceptions.rb +5 -3
- data/lib/action_dispatch/middleware/reloader.rb +7 -5
- data/lib/action_dispatch/middleware/remote_ip.rb +17 -16
- data/lib/action_dispatch/middleware/request_id.rb +2 -0
- data/lib/action_dispatch/middleware/server_timing.rb +4 -4
- data/lib/action_dispatch/middleware/session/abstract_store.rb +5 -0
- data/lib/action_dispatch/middleware/session/cache_store.rb +2 -0
- data/lib/action_dispatch/middleware/session/cookie_store.rb +11 -5
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +3 -1
- data/lib/action_dispatch/middleware/show_exceptions.rb +33 -19
- data/lib/action_dispatch/middleware/ssl.rb +18 -6
- data/lib/action_dispatch/middleware/stack.rb +7 -2
- data/lib/action_dispatch/middleware/static.rb +12 -8
- data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +7 -7
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +17 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +16 -12
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +3 -0
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +46 -37
- data/lib/action_dispatch/railtie.rb +13 -4
- data/lib/action_dispatch/request/session.rb +16 -6
- data/lib/action_dispatch/request/utils.rb +8 -3
- data/lib/action_dispatch/routing/inspector.rb +54 -6
- data/lib/action_dispatch/routing/mapper.rb +74 -26
- data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -0
- data/lib/action_dispatch/routing/redirection.rb +15 -6
- data/lib/action_dispatch/routing/route_set.rb +53 -23
- data/lib/action_dispatch/routing/routes_proxy.rb +10 -15
- data/lib/action_dispatch/routing/url_for.rb +5 -1
- data/lib/action_dispatch/routing.rb +7 -7
- data/lib/action_dispatch/system_test_case.rb +3 -3
- data/lib/action_dispatch/system_testing/browser.rb +25 -19
- data/lib/action_dispatch/system_testing/driver.rb +14 -22
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +27 -16
- data/lib/action_dispatch/testing/assertion_response.rb +1 -1
- data/lib/action_dispatch/testing/assertions/response.rb +13 -6
- data/lib/action_dispatch/testing/assertions/routing.rb +67 -28
- data/lib/action_dispatch/testing/assertions.rb +3 -1
- data/lib/action_dispatch/testing/integration.rb +27 -17
- data/lib/action_dispatch/testing/request_encoder.rb +4 -1
- data/lib/action_dispatch/testing/test_process.rb +4 -3
- data/lib/action_dispatch/testing/test_request.rb +1 -1
- data/lib/action_dispatch/testing/test_response.rb +23 -9
- data/lib/action_dispatch.rb +41 -4
- data/lib/action_pack/gem_version.rb +4 -4
- data/lib/action_pack/version.rb +1 -1
- data/lib/action_pack.rb +1 -1
- metadata +62 -26
@@ -4,6 +4,8 @@ require "active_support/core_ext/object/deep_dup"
|
|
4
4
|
require "active_support/core_ext/array/wrap"
|
5
5
|
|
6
6
|
module ActionDispatch # :nodoc:
|
7
|
+
# = Action Dispatch Content Security Policy
|
8
|
+
#
|
7
9
|
# Configures the HTTP
|
8
10
|
# {Content-Security-Policy}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy]
|
9
11
|
# response header to help protect against XSS and injection attacks.
|
@@ -26,16 +28,11 @@ module ActionDispatch # :nodoc:
|
|
26
28
|
end
|
27
29
|
|
28
30
|
class Middleware
|
29
|
-
CONTENT_TYPE = "Content-Type"
|
30
|
-
POLICY = "Content-Security-Policy"
|
31
|
-
POLICY_REPORT_ONLY = "Content-Security-Policy-Report-Only"
|
32
|
-
|
33
31
|
def initialize(app)
|
34
32
|
@app = app
|
35
33
|
end
|
36
34
|
|
37
35
|
def call(env)
|
38
|
-
request = ActionDispatch::Request.new env
|
39
36
|
status, headers, _ = response = @app.call(env)
|
40
37
|
|
41
38
|
# Returning CSP headers with a 304 Not Modified is harmful, since nonces in the new
|
@@ -44,6 +41,8 @@ module ActionDispatch # :nodoc:
|
|
44
41
|
|
45
42
|
return response if policy_present?(headers)
|
46
43
|
|
44
|
+
request = ActionDispatch::Request.new env
|
45
|
+
|
47
46
|
if policy = request.content_security_policy
|
48
47
|
nonce = request.content_security_policy_nonce
|
49
48
|
nonce_directives = request.content_security_policy_nonce_directives
|
@@ -57,14 +56,15 @@ module ActionDispatch # :nodoc:
|
|
57
56
|
private
|
58
57
|
def header_name(request)
|
59
58
|
if request.content_security_policy_report_only
|
60
|
-
|
59
|
+
ActionDispatch::Constants::CONTENT_SECURITY_POLICY_REPORT_ONLY
|
61
60
|
else
|
62
|
-
|
61
|
+
ActionDispatch::Constants::CONTENT_SECURITY_POLICY
|
63
62
|
end
|
64
63
|
end
|
65
64
|
|
66
65
|
def policy_present?(headers)
|
67
|
-
headers[
|
66
|
+
headers[ActionDispatch::Constants::CONTENT_SECURITY_POLICY] ||
|
67
|
+
headers[ActionDispatch::Constants::CONTENT_SECURITY_POLICY_REPORT_ONLY]
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
@@ -126,6 +126,7 @@ module ActionDispatch # :nodoc:
|
|
126
126
|
MAPPINGS = {
|
127
127
|
self: "'self'",
|
128
128
|
unsafe_eval: "'unsafe-eval'",
|
129
|
+
unsafe_hashes: "'unsafe-hashes'",
|
129
130
|
unsafe_inline: "'unsafe-inline'",
|
130
131
|
none: "'none'",
|
131
132
|
http: "http:",
|
@@ -4,6 +4,8 @@ require "active_support/parameter_filter"
|
|
4
4
|
|
5
5
|
module ActionDispatch
|
6
6
|
module Http
|
7
|
+
# = Action Dispatch HTTP Filter Parameters
|
8
|
+
#
|
7
9
|
# Allows you to specify sensitive query string and POST parameters to filter
|
8
10
|
# from the request log.
|
9
11
|
#
|
@@ -21,6 +23,7 @@ module ActionDispatch
|
|
21
23
|
@filtered_parameters = nil
|
22
24
|
@filtered_env = nil
|
23
25
|
@filtered_path = nil
|
26
|
+
@parameter_filter = nil
|
24
27
|
end
|
25
28
|
|
26
29
|
# Returns a hash of parameters with all sensitive data replaced.
|
@@ -40,13 +43,16 @@ module ActionDispatch
|
|
40
43
|
@filtered_path ||= query_string.empty? ? path : "#{path}?#{filtered_query_string}"
|
41
44
|
end
|
42
45
|
|
43
|
-
|
44
|
-
def parameter_filter
|
45
|
-
|
46
|
-
|
47
|
-
|
46
|
+
# Returns the +ActiveSupport::ParameterFilter+ object used to filter in this request.
|
47
|
+
def parameter_filter
|
48
|
+
@parameter_filter ||= if has_header?("action_dispatch.parameter_filter")
|
49
|
+
parameter_filter_for get_header("action_dispatch.parameter_filter")
|
50
|
+
else
|
51
|
+
NULL_PARAM_FILTER
|
52
|
+
end
|
48
53
|
end
|
49
54
|
|
55
|
+
private
|
50
56
|
def env_filter # :doc:
|
51
57
|
user_key = fetch_header("action_dispatch.parameter_filter") {
|
52
58
|
return NULL_ENV_FILTER
|
@@ -16,7 +16,20 @@ module ActionDispatch
|
|
16
16
|
|
17
17
|
included do
|
18
18
|
mattr_accessor :ignore_accept_header, default: false
|
19
|
-
|
19
|
+
|
20
|
+
def return_only_media_type_on_content_type=(value)
|
21
|
+
ActionDispatch.deprecator.warn(
|
22
|
+
"`config.action_dispatch.return_only_request_media_type_on_content_type` is deprecated and will" \
|
23
|
+
" be removed in Rails 7.2."
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
def return_only_media_type_on_content_type
|
28
|
+
ActionDispatch.deprecator.warn(
|
29
|
+
"`config.action_dispatch.return_only_request_media_type_on_content_type` is deprecated and will" \
|
30
|
+
" be removed in Rails 7.2."
|
31
|
+
)
|
32
|
+
end
|
20
33
|
end
|
21
34
|
|
22
35
|
# The MIME type of the HTTP request, such as Mime[:xml].
|
@@ -33,19 +46,6 @@ module ActionDispatch
|
|
33
46
|
end
|
34
47
|
end
|
35
48
|
|
36
|
-
def content_type
|
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
|
47
|
-
end
|
48
|
-
|
49
49
|
def has_content_type? # :nodoc:
|
50
50
|
get_header "CONTENT_TYPE"
|
51
51
|
end
|
@@ -72,7 +72,7 @@ module ActionDispatch
|
|
72
72
|
# GET /posts/5.xhtml | request.format => Mime[:html]
|
73
73
|
# GET /posts/5 | request.format => Mime[:html] or Mime[:js], or request.accepts.first
|
74
74
|
#
|
75
|
-
def format(
|
75
|
+
def format(_view_path = nil)
|
76
76
|
formats.first || Mime::NullType.instance
|
77
77
|
end
|
78
78
|
|
@@ -81,7 +81,7 @@ module ActionDispatch
|
|
81
81
|
v = if params_readable?
|
82
82
|
Array(Mime[parameters[:format]])
|
83
83
|
elsif use_accept_header && valid_accept_header
|
84
|
-
accepts
|
84
|
+
accepts.dup
|
85
85
|
elsif extension_format = format_from_path_extension
|
86
86
|
[extension_format]
|
87
87
|
elsif xhr?
|
@@ -90,7 +90,7 @@ module ActionDispatch
|
|
90
90
|
[Mime[:html]]
|
91
91
|
end
|
92
92
|
|
93
|
-
v
|
93
|
+
v.select! do |format|
|
94
94
|
format.symbol || format.ref == "*/*"
|
95
95
|
end
|
96
96
|
|
@@ -132,7 +132,7 @@ module ActionDispatch
|
|
132
132
|
# Sets the \formats by string extensions. This differs from #format= by allowing you
|
133
133
|
# to set multiple, ordered formats, which is useful when you want to have a fallback.
|
134
134
|
#
|
135
|
-
# In this example, the +:iphone+ format will be used if it's available, otherwise it'll
|
135
|
+
# In this example, the +:iphone+ format will be used if it's available, otherwise it'll fall back
|
136
136
|
# to the +:html+ format.
|
137
137
|
#
|
138
138
|
# class ApplicationController < ActionController::Base
|
@@ -172,22 +172,22 @@ module ActionDispatch
|
|
172
172
|
# in which case we assume you're a browser and send HTML.
|
173
173
|
BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/
|
174
174
|
|
175
|
-
def params_readable?
|
175
|
+
def params_readable?
|
176
176
|
parameters[:format]
|
177
177
|
rescue *RESCUABLE_MIME_FORMAT_ERRORS
|
178
178
|
false
|
179
179
|
end
|
180
180
|
|
181
|
-
def valid_accept_header
|
181
|
+
def valid_accept_header
|
182
182
|
(xhr? && (accept.present? || content_mime_type)) ||
|
183
183
|
(accept.present? && !accept.match?(BROWSER_LIKE_ACCEPTS))
|
184
184
|
end
|
185
185
|
|
186
|
-
def use_accept_header
|
186
|
+
def use_accept_header
|
187
187
|
!self.class.ignore_accept_header
|
188
188
|
end
|
189
189
|
|
190
|
-
def format_from_path_extension
|
190
|
+
def format_from_path_extension
|
191
191
|
path = get_header("action_dispatch.original_path") || get_header("PATH_INFO")
|
192
192
|
if match = path && path.match(/\.(\w+)\z/)
|
193
193
|
Mime[match.captures.first]
|
@@ -11,6 +11,7 @@ module Mime
|
|
11
11
|
def initialize
|
12
12
|
@mimes = []
|
13
13
|
@symbols = []
|
14
|
+
@symbols_set = Set.new
|
14
15
|
end
|
15
16
|
|
16
17
|
def each(&block)
|
@@ -19,17 +20,25 @@ module Mime
|
|
19
20
|
|
20
21
|
def <<(type)
|
21
22
|
@mimes << type
|
22
|
-
|
23
|
+
sym_type = type.to_sym
|
24
|
+
@symbols << sym_type
|
25
|
+
@symbols_set << sym_type
|
23
26
|
end
|
24
27
|
|
25
28
|
def delete_if
|
26
29
|
@mimes.delete_if do |x|
|
27
30
|
if yield x
|
28
|
-
|
31
|
+
sym_type = x.to_sym
|
32
|
+
@symbols.delete(sym_type)
|
33
|
+
@symbols_set.delete(sym_type)
|
29
34
|
true
|
30
35
|
end
|
31
36
|
end
|
32
37
|
end
|
38
|
+
|
39
|
+
def valid_symbols?(symbols) # :nodoc
|
40
|
+
symbols.all? { |s| @symbols_set.include?(s) }
|
41
|
+
end
|
33
42
|
end
|
34
43
|
|
35
44
|
SET = Mimes.new
|
@@ -42,6 +51,14 @@ module Mime
|
|
42
51
|
Type.lookup_by_extension(type)
|
43
52
|
end
|
44
53
|
|
54
|
+
def symbols
|
55
|
+
SET.symbols
|
56
|
+
end
|
57
|
+
|
58
|
+
def valid_symbols?(symbols) # :nodoc:
|
59
|
+
SET.valid_symbols?(symbols)
|
60
|
+
end
|
61
|
+
|
45
62
|
def fetch(type, &block)
|
46
63
|
return type if type.is_a?(Type)
|
47
64
|
EXTENSION_LOOKUP.fetch(type.to_s, &block)
|
@@ -135,13 +152,20 @@ module Mime
|
|
135
152
|
|
136
153
|
class << self
|
137
154
|
TRAILING_STAR_REGEXP = /^(text|application)\/\*/
|
138
|
-
|
155
|
+
# all media-type parameters need to be before the q-parameter
|
156
|
+
# https://www.rfc-editor.org/rfc/rfc7231#section-5.3.2
|
157
|
+
PARAMETER_SEPARATOR_REGEXP = /;\s*q="?/
|
158
|
+
ACCEPT_HEADER_REGEXP = /[^,\s"](?:[^,"]|"[^"]*")*/
|
139
159
|
|
140
160
|
def register_callback(&block)
|
141
161
|
@register_callbacks << block
|
142
162
|
end
|
143
163
|
|
144
164
|
def lookup(string)
|
165
|
+
return LOOKUP[string] if LOOKUP.key?(string)
|
166
|
+
|
167
|
+
# fallback to the media-type without parameters if it was not found
|
168
|
+
string = string.split(";", 2)[0]&.rstrip
|
145
169
|
LOOKUP[string] || Type.new(string)
|
146
170
|
end
|
147
171
|
|
@@ -171,12 +195,14 @@ module Mime
|
|
171
195
|
|
172
196
|
def parse(accept_header)
|
173
197
|
if !accept_header.include?(",")
|
174
|
-
|
175
|
-
|
176
|
-
|
198
|
+
if (index = accept_header.index(PARAMETER_SEPARATOR_REGEXP))
|
199
|
+
accept_header = accept_header[0, index].strip
|
200
|
+
end
|
201
|
+
return [] if accept_header.blank?
|
202
|
+
parse_trailing_star(accept_header) || Array(Mime::Type.lookup(accept_header))
|
177
203
|
else
|
178
204
|
list, index = [], 0
|
179
|
-
accept_header.
|
205
|
+
accept_header.scan(ACCEPT_HEADER_REGEXP).each do |header|
|
180
206
|
params, q = header.split(PARAMETER_SEPARATOR_REGEXP)
|
181
207
|
|
182
208
|
next unless params
|
@@ -199,10 +225,10 @@ module Mime
|
|
199
225
|
end
|
200
226
|
|
201
227
|
# For an input of <tt>'text'</tt>, returns <tt>[Mime[:json], Mime[:xml], Mime[:ics],
|
202
|
-
# Mime[:html], Mime[:css], Mime[:csv], Mime[:js], Mime[:yaml], Mime[:text]</tt>.
|
228
|
+
# Mime[:html], Mime[:css], Mime[:csv], Mime[:js], Mime[:yaml], Mime[:text]]</tt>.
|
203
229
|
#
|
204
230
|
# For an input of <tt>'application'</tt>, returns <tt>[Mime[:html], Mime[:js],
|
205
|
-
# Mime[:xml], Mime[:yaml], Mime[:atom], Mime[:json], Mime[:rss], Mime[:url_encoded_form]</tt>.
|
231
|
+
# Mime[:xml], Mime[:yaml], Mime[:atom], Mime[:json], Mime[:rss], Mime[:url_encoded_form]]</tt>.
|
206
232
|
def parse_data_with_trailing_star(type)
|
207
233
|
Mime::SET.select { |m| m.match?(type) }
|
208
234
|
end
|
@@ -225,7 +251,7 @@ module Mime
|
|
225
251
|
attr_reader :hash
|
226
252
|
|
227
253
|
MIME_NAME = "[a-zA-Z0-9][a-zA-Z0-9#{Regexp.escape('!#$&-^_.+')}]{0,126}"
|
228
|
-
MIME_PARAMETER_VALUE = "
|
254
|
+
MIME_PARAMETER_VALUE = "(?:#{MIME_NAME}|\"[^\"\r\\\\]*\")"
|
229
255
|
MIME_PARAMETER = "\s*;\s*#{MIME_NAME}(?:=#{MIME_PARAMETER_VALUE})?"
|
230
256
|
MIME_REGEXP = /\A(?:\*\/\*|#{MIME_NAME}\/(?:\*|#{MIME_NAME})(?>#{MIME_PARAMETER})*\s*)\z/
|
231
257
|
|
@@ -291,7 +317,7 @@ module Mime
|
|
291
317
|
end
|
292
318
|
|
293
319
|
def html?
|
294
|
-
(symbol == :html) ||
|
320
|
+
(symbol == :html) || @string.include?("html")
|
295
321
|
end
|
296
322
|
|
297
323
|
def all?; false; end
|
@@ -18,6 +18,7 @@ Mime::Type.register "image/gif", :gif, [], %w(gif)
|
|
18
18
|
Mime::Type.register "image/bmp", :bmp, [], %w(bmp)
|
19
19
|
Mime::Type.register "image/tiff", :tiff, [], %w(tif tiff)
|
20
20
|
Mime::Type.register "image/svg+xml", :svg
|
21
|
+
Mime::Type.register "image/webp", :webp, [], %w(webp)
|
21
22
|
|
22
23
|
Mime::Type.register "video/mpeg", :mpeg, [], %w(mpg mpeg mpe)
|
23
24
|
|
@@ -43,7 +44,8 @@ Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form
|
|
43
44
|
|
44
45
|
# https://www.ietf.org/rfc/rfc4627.txt
|
45
46
|
# http://www.json.org/JSONRequest.html
|
46
|
-
|
47
|
+
# https://www.ietf.org/rfc/rfc7807.txt
|
48
|
+
Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest application/problem+json )
|
47
49
|
|
48
50
|
Mime::Type.register "application/pdf", :pdf, [], %w(pdf)
|
49
51
|
Mime::Type.register "application/zip", :zip, [], %w(zip)
|
@@ -76,7 +76,7 @@ module ActionDispatch
|
|
76
76
|
end
|
77
77
|
|
78
78
|
# Returns a hash with the \parameters used to form the \path of the request.
|
79
|
-
# Returned hash keys are
|
79
|
+
# Returned hash keys are symbols:
|
80
80
|
#
|
81
81
|
# { action: "my_action", controller: "my_controller" }
|
82
82
|
def path_parameters
|
@@ -3,6 +3,8 @@
|
|
3
3
|
require "active_support/core_ext/object/deep_dup"
|
4
4
|
|
5
5
|
module ActionDispatch # :nodoc:
|
6
|
+
# = Action Dispatch \PermissionsPolicy
|
7
|
+
#
|
6
8
|
# Configures the HTTP
|
7
9
|
# {Feature-Policy}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy]
|
8
10
|
# response header to specify which browser features the current document and
|
@@ -19,32 +21,30 @@ module ActionDispatch # :nodoc:
|
|
19
21
|
# policy.payment :self, "https://secure.example.com"
|
20
22
|
# end
|
21
23
|
#
|
24
|
+
# The Feature-Policy header has been renamed to Permissions-Policy.
|
25
|
+
# The Permissions-Policy requires a different implementation and isn't
|
26
|
+
# yet supported by all browsers. To avoid having to rename this
|
27
|
+
# middleware in the future we use the new name for the middleware but
|
28
|
+
# keep the old header name and implementation for now.
|
22
29
|
class PermissionsPolicy
|
23
30
|
class Middleware
|
24
|
-
CONTENT_TYPE = "Content-Type"
|
25
|
-
# The Feature-Policy header has been renamed to Permissions-Policy.
|
26
|
-
# The Permissions-Policy requires a different implementation and isn't
|
27
|
-
# yet supported by all browsers. To avoid having to rename this
|
28
|
-
# middleware in the future we use the new name for the middleware but
|
29
|
-
# keep the old header name and implementation for now.
|
30
|
-
POLICY = "Feature-Policy"
|
31
|
-
|
32
31
|
def initialize(app)
|
33
32
|
@app = app
|
34
33
|
end
|
35
34
|
|
36
35
|
def call(env)
|
37
|
-
request = ActionDispatch::Request.new(env)
|
38
36
|
_, headers, _ = response = @app.call(env)
|
39
37
|
|
40
38
|
return response if policy_present?(headers)
|
41
39
|
|
40
|
+
request = ActionDispatch::Request.new(env)
|
41
|
+
|
42
42
|
if policy = request.permissions_policy
|
43
|
-
headers[
|
43
|
+
headers[ActionDispatch::Constants::FEATURE_POLICY] = policy.build(request.controller_instance)
|
44
44
|
end
|
45
45
|
|
46
46
|
if policy_empty?(policy)
|
47
|
-
headers.delete(
|
47
|
+
headers.delete(ActionDispatch::Constants::FEATURE_POLICY)
|
48
48
|
end
|
49
49
|
|
50
50
|
response
|
@@ -52,7 +52,7 @@ module ActionDispatch # :nodoc:
|
|
52
52
|
|
53
53
|
private
|
54
54
|
def policy_present?(headers)
|
55
|
-
headers[
|
55
|
+
headers[ActionDispatch::Constants::FEATURE_POLICY]
|
56
56
|
end
|
57
57
|
|
58
58
|
def policy_empty?(policy)
|
@@ -78,7 +78,7 @@ module ActionDispatch # :nodoc:
|
|
78
78
|
}.freeze
|
79
79
|
|
80
80
|
# List of available permissions can be found at
|
81
|
-
# https://github.com/w3c/webappsec-permissions-policy/blob/
|
81
|
+
# https://github.com/w3c/webappsec-permissions-policy/blob/main/features.md#policy-controlled-features
|
82
82
|
DIRECTIVES = {
|
83
83
|
accelerometer: "accelerometer",
|
84
84
|
ambient_light_sensor: "ambient-light-sensor",
|
@@ -88,15 +88,18 @@ module ActionDispatch # :nodoc:
|
|
88
88
|
fullscreen: "fullscreen",
|
89
89
|
geolocation: "geolocation",
|
90
90
|
gyroscope: "gyroscope",
|
91
|
+
hid: "hid",
|
92
|
+
idle_detection: "idle-detection",
|
91
93
|
magnetometer: "magnetometer",
|
92
94
|
microphone: "microphone",
|
93
95
|
midi: "midi",
|
94
96
|
payment: "payment",
|
95
97
|
picture_in_picture: "picture-in-picture",
|
96
|
-
|
98
|
+
screen_wake_lock: "screen-wake-lock",
|
99
|
+
serial: "serial",
|
100
|
+
sync_xhr: "sync-xhr",
|
97
101
|
usb: "usb",
|
98
|
-
|
99
|
-
vr: "vr",
|
102
|
+
web_share: "web-share",
|
100
103
|
}.freeze
|
101
104
|
|
102
105
|
private_constant :MAPPINGS, :DIRECTIVES
|
@@ -122,6 +125,25 @@ module ActionDispatch # :nodoc:
|
|
122
125
|
end
|
123
126
|
end
|
124
127
|
|
128
|
+
%w[speaker vibrate vr].each do |directive|
|
129
|
+
define_method(directive) do |*sources|
|
130
|
+
ActionDispatch.deprecator.warn(<<~MSG)
|
131
|
+
The `#{directive}` permissions policy directive is deprecated
|
132
|
+
and will be removed in Rails 7.2.
|
133
|
+
|
134
|
+
There is no browser support for this directive, and no plan
|
135
|
+
for browser support in the future. You can just remove this
|
136
|
+
directive from your application.
|
137
|
+
MSG
|
138
|
+
|
139
|
+
if sources.first
|
140
|
+
@directives[directive] = apply_mappings(sources)
|
141
|
+
else
|
142
|
+
@directives.delete(directive)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
125
147
|
def build(context = nil)
|
126
148
|
build_directives(context).compact.join("; ")
|
127
149
|
end
|
@@ -72,7 +72,7 @@ module ActionDispatch
|
|
72
72
|
|
73
73
|
PASS_NOT_FOUND = Class.new { # :nodoc:
|
74
74
|
def self.action(_); self; end
|
75
|
-
def self.call(_); [404, {
|
75
|
+
def self.call(_); [404, { Constants::X_CASCADE => "pass" }, []]; end
|
76
76
|
def self.action_encoding_template(action); false; end
|
77
77
|
}
|
78
78
|
|
@@ -145,6 +145,18 @@ module ActionDispatch
|
|
145
145
|
@request_method ||= check_method(super)
|
146
146
|
end
|
147
147
|
|
148
|
+
# Returns the URI pattern of the matched route for the request,
|
149
|
+
# using the same format as `bin/rails routes`:
|
150
|
+
#
|
151
|
+
# request.route_uri_pattern # => "/:controller(/:action(/:id))(.:format)"
|
152
|
+
def route_uri_pattern
|
153
|
+
get_header("action_dispatch.route_uri_pattern")
|
154
|
+
end
|
155
|
+
|
156
|
+
def route_uri_pattern=(pattern) # :nodoc:
|
157
|
+
set_header("action_dispatch.route_uri_pattern", pattern)
|
158
|
+
end
|
159
|
+
|
148
160
|
def routes # :nodoc:
|
149
161
|
get_header("action_dispatch.routes")
|
150
162
|
end
|
@@ -179,13 +191,6 @@ module ActionDispatch
|
|
179
191
|
get_header "action_dispatch.http_auth_salt"
|
180
192
|
end
|
181
193
|
|
182
|
-
def show_exceptions? # :nodoc:
|
183
|
-
# We're treating `nil` as "unset", and we want the default setting to be
|
184
|
-
# `true`. This logic should be extracted to `env_config` and calculated
|
185
|
-
# once.
|
186
|
-
!(get_header("action_dispatch.show_exceptions") == false)
|
187
|
-
end
|
188
|
-
|
189
194
|
# Returns a symbol form of the #request_method.
|
190
195
|
def request_method_symbol
|
191
196
|
HTTP_METHOD_LOOKUP[request_method]
|
@@ -194,9 +199,20 @@ module ActionDispatch
|
|
194
199
|
# Returns the original value of the environment's REQUEST_METHOD,
|
195
200
|
# even if it was overridden by middleware. See #request_method for
|
196
201
|
# more information.
|
197
|
-
|
198
|
-
|
202
|
+
#
|
203
|
+
# For debugging purposes, when called with arguments this method will
|
204
|
+
# fall back to Object#method
|
205
|
+
def method(*args)
|
206
|
+
if args.empty?
|
207
|
+
@method ||= check_method(
|
208
|
+
get_header("rack.methodoverride.original_method") ||
|
209
|
+
get_header("REQUEST_METHOD")
|
210
|
+
)
|
211
|
+
else
|
212
|
+
super
|
213
|
+
end
|
199
214
|
end
|
215
|
+
ruby2_keywords(:method)
|
200
216
|
|
201
217
|
# Returns a symbol form of the #method.
|
202
218
|
def method_symbol
|
@@ -213,11 +229,12 @@ module ActionDispatch
|
|
213
229
|
# Early Hints is an HTTP/2 status code that indicates hints to help a client start
|
214
230
|
# making preparations for processing the final response.
|
215
231
|
#
|
216
|
-
# If the env contains +rack.early_hints+ then the server accepts HTTP2 push for
|
232
|
+
# If the env contains +rack.early_hints+ then the server accepts HTTP2 push for
|
233
|
+
# link headers.
|
217
234
|
#
|
218
235
|
# The +send_early_hints+ method accepts a hash of links as follows:
|
219
236
|
#
|
220
|
-
# send_early_hints("
|
237
|
+
# send_early_hints("link" => "</style.css>; rel=preload; as=style,</script.js>; rel=preload")
|
221
238
|
#
|
222
239
|
# If you are using +javascript_include_tag+ or +stylesheet_link_tag+ the
|
223
240
|
# Early Hints headers are included by default if supported.
|
@@ -267,6 +284,7 @@ module ActionDispatch
|
|
267
284
|
|
268
285
|
# Returns the content length of the request as an integer.
|
269
286
|
def content_length
|
287
|
+
return raw_post.bytesize if headers.key?("Transfer-Encoding")
|
270
288
|
super.to_i
|
271
289
|
end
|
272
290
|
|
@@ -321,9 +339,7 @@ module ActionDispatch
|
|
321
339
|
# work with raw requests directly.
|
322
340
|
def raw_post
|
323
341
|
unless has_header? "RAW_POST_DATA"
|
324
|
-
|
325
|
-
set_header("RAW_POST_DATA", raw_post_body.read(content_length))
|
326
|
-
raw_post_body.rewind if raw_post_body.respond_to?(:rewind)
|
342
|
+
set_header("RAW_POST_DATA", read_body_stream)
|
327
343
|
end
|
328
344
|
get_header "RAW_POST_DATA"
|
329
345
|
end
|
@@ -357,6 +373,7 @@ module ActionDispatch
|
|
357
373
|
|
358
374
|
def reset_session
|
359
375
|
session.destroy
|
376
|
+
reset_csrf_token
|
360
377
|
end
|
361
378
|
|
362
379
|
def session=(session) # :nodoc:
|
@@ -428,15 +445,52 @@ module ActionDispatch
|
|
428
445
|
"#<#{self.class.name} #{method} #{original_url.dump} for #{remote_ip}>"
|
429
446
|
end
|
430
447
|
|
448
|
+
def reset_csrf_token
|
449
|
+
controller_instance.reset_csrf_token(self) if controller_instance.respond_to?(:reset_csrf_token)
|
450
|
+
end
|
451
|
+
|
452
|
+
def commit_csrf_token
|
453
|
+
controller_instance.commit_csrf_token(self) if controller_instance.respond_to?(:commit_csrf_token)
|
454
|
+
end
|
455
|
+
|
431
456
|
private
|
432
457
|
def check_method(name)
|
433
|
-
|
458
|
+
if name
|
459
|
+
HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS[0...-1].join(', ')}, and #{HTTP_METHODS[-1]}")
|
460
|
+
end
|
461
|
+
|
434
462
|
name
|
435
463
|
end
|
436
464
|
|
437
465
|
def default_session
|
438
466
|
Session.disabled(self)
|
439
467
|
end
|
468
|
+
|
469
|
+
def read_body_stream
|
470
|
+
if body_stream
|
471
|
+
reset_stream(body_stream) do
|
472
|
+
if headers.key?("Transfer-Encoding")
|
473
|
+
body_stream.read # Read body stream until EOF if "Transfer-Encoding" is present
|
474
|
+
else
|
475
|
+
body_stream.read(content_length)
|
476
|
+
end
|
477
|
+
end
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
481
|
+
def reset_stream(body_stream)
|
482
|
+
if body_stream.respond_to?(:rewind)
|
483
|
+
body_stream.rewind
|
484
|
+
|
485
|
+
content = yield
|
486
|
+
|
487
|
+
body_stream.rewind
|
488
|
+
|
489
|
+
content
|
490
|
+
else
|
491
|
+
yield
|
492
|
+
end
|
493
|
+
end
|
440
494
|
end
|
441
495
|
end
|
442
496
|
|