actionpack 7.0.4 → 7.1.3.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 +397 -269
- data/MIT-LICENSE +1 -1
- data/README.rdoc +4 -4
- 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 +75 -28
- data/lib/abstract_controller/railties/routes_helpers.rb +1 -16
- data/lib/abstract_controller/rendering.rb +12 -14
- data/lib/abstract_controller/translation.rb +9 -6
- data/lib/abstract_controller/url_for.rb +2 -0
- data/lib/abstract_controller.rb +6 -0
- data/lib/action_controller/api.rb +6 -4
- 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/basic_implicit_render.rb +3 -1
- data/lib/action_controller/metal/conditional_get.rb +121 -123
- data/lib/action_controller/metal/content_security_policy.rb +5 -5
- data/lib/action_controller/metal/data_streaming.rb +20 -18
- data/lib/action_controller/metal/default_headers.rb +2 -0
- data/lib/action_controller/metal/etag_with_flash.rb +3 -1
- 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 +9 -7
- data/lib/action_controller/metal/helpers.rb +3 -14
- data/lib/action_controller/metal/http_authentication.rb +17 -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 +25 -1
- 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 +2 -2
- data/lib/action_controller/metal/redirecting.rb +29 -8
- data/lib/action_controller/metal/renderers.rb +4 -4
- data/lib/action_controller/metal/rendering.rb +114 -9
- data/lib/action_controller/metal/request_forgery_protection.rb +144 -53
- data/lib/action_controller/metal/rescue.rb +6 -3
- data/lib/action_controller/metal/streaming.rb +71 -31
- data/lib/action_controller/metal/strong_parameters.rb +158 -101
- data/lib/action_controller/metal/url_for.rb +9 -4
- data/lib/action_controller/metal.rb +79 -21
- data/lib/action_controller/railtie.rb +24 -10
- data/lib/action_controller/renderer.rb +99 -85
- data/lib/action_controller/test_case.rb +15 -5
- data/lib/action_controller.rb +8 -1
- data/lib/action_dispatch/constants.rb +32 -0
- data/lib/action_dispatch/deprecator.rb +7 -0
- data/lib/action_dispatch/http/cache.rb +9 -11
- data/lib/action_dispatch/http/content_security_policy.rb +14 -9
- data/lib/action_dispatch/http/filter_parameters.rb +14 -28
- data/lib/action_dispatch/http/headers.rb +3 -1
- data/lib/action_dispatch/http/mime_negotiation.rb +22 -22
- data/lib/action_dispatch/http/mime_type.rb +35 -12
- 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 -23
- data/lib/action_dispatch/http/rack_cache.rb +2 -0
- data/lib/action_dispatch/http/request.rb +63 -30
- data/lib/action_dispatch/http/response.rb +80 -63
- data/lib/action_dispatch/http/upload.rb +15 -2
- 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 +108 -117
- 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 +1 -1
- data/lib/action_dispatch/middleware/flash.rb +7 -0
- data/lib/action_dispatch/middleware/host_authorization.rb +18 -8
- 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 +21 -20
- data/lib/action_dispatch/middleware/request_id.rb +4 -2
- 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 +25 -18
- 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 +14 -10
- 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/blocked_host.html.erb +7 -3
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -3
- 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 +59 -41
- data/lib/action_dispatch/railtie.rb +14 -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 +58 -24
- 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 +52 -22
- data/lib/action_dispatch/routing/routes_proxy.rb +10 -15
- data/lib/action_dispatch/routing/url_for.rb +26 -22
- 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 +20 -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 +14 -7
- 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 +37 -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 +65 -29
@@ -18,7 +18,7 @@ module ActionDispatch
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def if_none_match_etags
|
21
|
-
if_none_match ? if_none_match.split(
|
21
|
+
if_none_match ? if_none_match.split(",").each(&:strip!) : []
|
22
22
|
end
|
23
23
|
|
24
24
|
def not_modified?(modified_at)
|
@@ -32,8 +32,8 @@ module ActionDispatch
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
-
# Check response freshness (Last-Modified and ETag) against request
|
36
|
-
# If-Modified-Since and If-None-Match conditions. If both headers are
|
35
|
+
# Check response freshness (+Last-Modified+ and ETag) against request
|
36
|
+
# +If-Modified-Since+ and +If-None-Match+ conditions. If both headers are
|
37
37
|
# supplied, both must match, or the request is not considered fresh.
|
38
38
|
def fresh?(response)
|
39
39
|
last_modified = if_modified_since
|
@@ -81,8 +81,8 @@ module ActionDispatch
|
|
81
81
|
|
82
82
|
# This method sets a weak ETag validator on the response so browsers
|
83
83
|
# and proxies may cache the response, keyed on the ETag. On subsequent
|
84
|
-
# requests, the If-None-Match header is set to the cached ETag. If it
|
85
|
-
# matches the current ETag, we can return a 304 Not Modified response
|
84
|
+
# requests, the +If-None-Match+ header is set to the cached ETag. If it
|
85
|
+
# matches the current ETag, we can return a <tt>304 Not Modified</tt> response
|
86
86
|
# with no body, letting the browser or proxy know that their cache is
|
87
87
|
# current. Big savings in request time and network bandwidth.
|
88
88
|
#
|
@@ -92,7 +92,7 @@ module ActionDispatch
|
|
92
92
|
# is viewing.
|
93
93
|
#
|
94
94
|
# Strong ETags are considered byte-for-byte identical. They allow a
|
95
|
-
# browser or proxy cache to support Range requests, useful for paging
|
95
|
+
# browser or proxy cache to support +Range+ requests, useful for paging
|
96
96
|
# through a PDF file or scrubbing through a video. Some CDNs only
|
97
97
|
# support strong ETags and will ignore weak ETags entirely.
|
98
98
|
#
|
@@ -112,12 +112,12 @@ module ActionDispatch
|
|
112
112
|
|
113
113
|
def etag?; etag; end
|
114
114
|
|
115
|
-
# True if an ETag is set and it's a weak validator (preceded with W
|
115
|
+
# True if an ETag is set, and it's a weak validator (preceded with <tt>W/</tt>).
|
116
116
|
def weak_etag?
|
117
117
|
etag? && etag.start_with?('W/"')
|
118
118
|
end
|
119
119
|
|
120
|
-
# True if an ETag is set and it isn't a weak validator (not preceded with W
|
120
|
+
# True if an ETag is set, and it isn't a weak validator (not preceded with <tt>W/</tt>).
|
121
121
|
def strong_etag?
|
122
122
|
etag? && !weak_etag?
|
123
123
|
end
|
@@ -138,15 +138,13 @@ module ActionDispatch
|
|
138
138
|
def cache_control_segments
|
139
139
|
if cache_control = _cache_control
|
140
140
|
cache_control.delete(" ").split(",")
|
141
|
-
else
|
142
|
-
[]
|
143
141
|
end
|
144
142
|
end
|
145
143
|
|
146
144
|
def cache_control_headers
|
147
145
|
cache_control = {}
|
148
146
|
|
149
|
-
cache_control_segments
|
147
|
+
cache_control_segments&.each do |segment|
|
150
148
|
directive, argument = segment.split("=", 2)
|
151
149
|
|
152
150
|
if SPECIAL_KEYS.include? directive
|
@@ -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.
|
@@ -23,20 +25,21 @@ module ActionDispatch # :nodoc:
|
|
23
25
|
# end
|
24
26
|
class ContentSecurityPolicy
|
25
27
|
class Middleware
|
26
|
-
CONTENT_TYPE = "Content-Type"
|
27
|
-
POLICY = "Content-Security-Policy"
|
28
|
-
POLICY_REPORT_ONLY = "Content-Security-Policy-Report-Only"
|
29
|
-
|
30
28
|
def initialize(app)
|
31
29
|
@app = app
|
32
30
|
end
|
33
31
|
|
34
32
|
def call(env)
|
35
|
-
|
36
|
-
|
33
|
+
status, headers, _ = response = @app.call(env)
|
34
|
+
|
35
|
+
# Returning CSP headers with a 304 Not Modified is harmful, since nonces in the new
|
36
|
+
# CSP headers might not match nonces in the cached HTML.
|
37
|
+
return response if status == 304
|
37
38
|
|
38
39
|
return response if policy_present?(headers)
|
39
40
|
|
41
|
+
request = ActionDispatch::Request.new env
|
42
|
+
|
40
43
|
if policy = request.content_security_policy
|
41
44
|
nonce = request.content_security_policy_nonce
|
42
45
|
nonce_directives = request.content_security_policy_nonce_directives
|
@@ -50,14 +53,15 @@ module ActionDispatch # :nodoc:
|
|
50
53
|
private
|
51
54
|
def header_name(request)
|
52
55
|
if request.content_security_policy_report_only
|
53
|
-
|
56
|
+
ActionDispatch::Constants::CONTENT_SECURITY_POLICY_REPORT_ONLY
|
54
57
|
else
|
55
|
-
|
58
|
+
ActionDispatch::Constants::CONTENT_SECURITY_POLICY
|
56
59
|
end
|
57
60
|
end
|
58
61
|
|
59
62
|
def policy_present?(headers)
|
60
|
-
headers[
|
63
|
+
headers[ActionDispatch::Constants::CONTENT_SECURITY_POLICY] ||
|
64
|
+
headers[ActionDispatch::Constants::CONTENT_SECURITY_POLICY_REPORT_ONLY]
|
61
65
|
end
|
62
66
|
end
|
63
67
|
|
@@ -119,6 +123,7 @@ module ActionDispatch # :nodoc:
|
|
119
123
|
MAPPINGS = {
|
120
124
|
self: "'self'",
|
121
125
|
unsafe_eval: "'unsafe-eval'",
|
126
|
+
unsafe_hashes: "'unsafe-hashes'",
|
122
127
|
unsafe_inline: "'unsafe-inline'",
|
123
128
|
none: "'none'",
|
124
129
|
http: "http:",
|
@@ -4,33 +4,15 @@ require "active_support/parameter_filter"
|
|
4
4
|
|
5
5
|
module ActionDispatch
|
6
6
|
module Http
|
7
|
-
#
|
8
|
-
# the request log by looking in the query string of the request and all
|
9
|
-
# sub-hashes of the params hash to filter. Filtering only certain sub-keys
|
10
|
-
# from a hash is possible by using the dot notation: 'credit_card.number'.
|
11
|
-
# If a block is given, each key and value of the params hash and all
|
12
|
-
# sub-hashes are passed to it, where the value or the key can be replaced using
|
13
|
-
# String#replace or similar methods.
|
7
|
+
# = Action Dispatch HTTP Filter Parameters
|
14
8
|
#
|
15
|
-
#
|
16
|
-
#
|
9
|
+
# Allows you to specify sensitive query string and POST parameters to filter
|
10
|
+
# from the request log.
|
17
11
|
#
|
12
|
+
# # Replaces values with "[FILTERED]" for keys that match /foo|bar/i.
|
18
13
|
# env["action_dispatch.parameter_filter"] = [:foo, "bar"]
|
19
|
-
# => replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
|
20
14
|
#
|
21
|
-
#
|
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
|
-
#
|
26
|
-
# env["action_dispatch.parameter_filter"] = [ "credit_card.code" ]
|
27
|
-
# => replaces { credit_card: {code: "xxxx"} } with "[FILTERED]", does not
|
28
|
-
# change { file: { code: "xxxx"} }
|
29
|
-
#
|
30
|
-
# env["action_dispatch.parameter_filter"] = -> (k, v) do
|
31
|
-
# v.reverse! if k.match?(/secret/i)
|
32
|
-
# end
|
33
|
-
# => reverses the value to all keys matching /secret/i
|
15
|
+
# For more information about filter behavior, see ActiveSupport::ParameterFilter.
|
34
16
|
module FilterParameters
|
35
17
|
ENV_MATCH = [/RAW_POST_DATA/, "rack.request.form_vars"] # :nodoc:
|
36
18
|
NULL_PARAM_FILTER = ActiveSupport::ParameterFilter.new # :nodoc:
|
@@ -41,6 +23,7 @@ module ActionDispatch
|
|
41
23
|
@filtered_parameters = nil
|
42
24
|
@filtered_env = nil
|
43
25
|
@filtered_path = nil
|
26
|
+
@parameter_filter = nil
|
44
27
|
end
|
45
28
|
|
46
29
|
# Returns a hash of parameters with all sensitive data replaced.
|
@@ -60,13 +43,16 @@ module ActionDispatch
|
|
60
43
|
@filtered_path ||= query_string.empty? ? path : "#{path}?#{filtered_query_string}"
|
61
44
|
end
|
62
45
|
|
63
|
-
|
64
|
-
def parameter_filter
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
68
53
|
end
|
69
54
|
|
55
|
+
private
|
70
56
|
def env_filter # :doc:
|
71
57
|
user_key = fetch_header("action_dispatch.parameter_filter") {
|
72
58
|
return NULL_ENV_FILTER
|
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
module ActionDispatch
|
4
4
|
module Http
|
5
|
+
# = Action Dispatch HTTP \Headers
|
6
|
+
#
|
5
7
|
# Provides access to the request's HTTP headers from the environment.
|
6
8
|
#
|
7
9
|
# env = { "CONTENT_TYPE" => "text/plain", "HTTP_USER_AGENT" => "curl/7.43.0" }
|
@@ -65,7 +67,7 @@ module ActionDispatch
|
|
65
67
|
@req.set_header env_name(key), value
|
66
68
|
end
|
67
69
|
|
68
|
-
# Add a value to a multivalued header like Vary or Accept-Encoding
|
70
|
+
# Add a value to a multivalued header like +Vary+ or +Accept-Encoding+.
|
69
71
|
def add(key, value)
|
70
72
|
@req.add_header env_name(key), value
|
71
73
|
end
|
@@ -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,14 +152,18 @@ 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)
|
145
|
-
|
165
|
+
# fallback to the media-type without parameters if it was not found
|
166
|
+
LOOKUP[string] || LOOKUP[string.split(";", 2)[0]&.rstrip] || Type.new(string)
|
146
167
|
end
|
147
168
|
|
148
169
|
def lookup_by_extension(extension)
|
@@ -171,12 +192,14 @@ module Mime
|
|
171
192
|
|
172
193
|
def parse(accept_header)
|
173
194
|
if !accept_header.include?(",")
|
174
|
-
|
175
|
-
|
176
|
-
|
195
|
+
if (index = accept_header.index(PARAMETER_SEPARATOR_REGEXP))
|
196
|
+
accept_header = accept_header[0, index].strip
|
197
|
+
end
|
198
|
+
return [] if accept_header.blank?
|
199
|
+
parse_trailing_star(accept_header) || Array(Mime::Type.lookup(accept_header))
|
177
200
|
else
|
178
201
|
list, index = [], 0
|
179
|
-
accept_header.
|
202
|
+
accept_header.scan(ACCEPT_HEADER_REGEXP).each do |header|
|
180
203
|
params, q = header.split(PARAMETER_SEPARATOR_REGEXP)
|
181
204
|
|
182
205
|
next unless params
|
@@ -199,10 +222,10 @@ module Mime
|
|
199
222
|
end
|
200
223
|
|
201
224
|
# 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>.
|
225
|
+
# Mime[:html], Mime[:css], Mime[:csv], Mime[:js], Mime[:yaml], Mime[:text]]</tt>.
|
203
226
|
#
|
204
227
|
# 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>.
|
228
|
+
# Mime[:xml], Mime[:yaml], Mime[:atom], Mime[:json], Mime[:rss], Mime[:url_encoded_form]]</tt>.
|
206
229
|
def parse_data_with_trailing_star(type)
|
207
230
|
Mime::SET.select { |m| m.match?(type) }
|
208
231
|
end
|
@@ -225,7 +248,7 @@ module Mime
|
|
225
248
|
attr_reader :hash
|
226
249
|
|
227
250
|
MIME_NAME = "[a-zA-Z0-9][a-zA-Z0-9#{Regexp.escape('!#$&-^_.+')}]{0,126}"
|
228
|
-
MIME_PARAMETER_VALUE = "
|
251
|
+
MIME_PARAMETER_VALUE = "(?:#{MIME_NAME}|\"[^\"\r\\\\]*\")"
|
229
252
|
MIME_PARAMETER = "\s*;\s*#{MIME_NAME}(?:=#{MIME_PARAMETER_VALUE})?"
|
230
253
|
MIME_REGEXP = /\A(?:\*\/\*|#{MIME_NAME}\/(?:\*|#{MIME_NAME})(?>#{MIME_PARAMETER})*\s*)\z/
|
231
254
|
|
@@ -291,7 +314,7 @@ module Mime
|
|
291
314
|
end
|
292
315
|
|
293
316
|
def html?
|
294
|
-
(symbol == :html) ||
|
317
|
+
(symbol == :html) || @string.include?("html")
|
295
318
|
end
|
296
319
|
|
297
320
|
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,47 +21,38 @@ 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
|
-
return response unless html_response?(headers)
|
41
38
|
return response if policy_present?(headers)
|
42
39
|
|
40
|
+
request = ActionDispatch::Request.new(env)
|
41
|
+
|
43
42
|
if policy = request.permissions_policy
|
44
|
-
headers[
|
43
|
+
headers[ActionDispatch::Constants::FEATURE_POLICY] = policy.build(request.controller_instance)
|
45
44
|
end
|
46
45
|
|
47
46
|
if policy_empty?(policy)
|
48
|
-
headers.delete(
|
47
|
+
headers.delete(ActionDispatch::Constants::FEATURE_POLICY)
|
49
48
|
end
|
50
49
|
|
51
50
|
response
|
52
51
|
end
|
53
52
|
|
54
53
|
private
|
55
|
-
def html_response?(headers)
|
56
|
-
if content_type = headers[CONTENT_TYPE]
|
57
|
-
/html/.match?(content_type)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
54
|
def policy_present?(headers)
|
62
|
-
headers[
|
55
|
+
headers[ActionDispatch::Constants::FEATURE_POLICY]
|
63
56
|
end
|
64
57
|
|
65
58
|
def policy_empty?(policy)
|
@@ -85,7 +78,7 @@ module ActionDispatch # :nodoc:
|
|
85
78
|
}.freeze
|
86
79
|
|
87
80
|
# List of available permissions can be found at
|
88
|
-
# https://github.com/w3c/webappsec-permissions-policy/blob/
|
81
|
+
# https://github.com/w3c/webappsec-permissions-policy/blob/main/features.md#policy-controlled-features
|
89
82
|
DIRECTIVES = {
|
90
83
|
accelerometer: "accelerometer",
|
91
84
|
ambient_light_sensor: "ambient-light-sensor",
|
@@ -95,15 +88,18 @@ module ActionDispatch # :nodoc:
|
|
95
88
|
fullscreen: "fullscreen",
|
96
89
|
geolocation: "geolocation",
|
97
90
|
gyroscope: "gyroscope",
|
91
|
+
hid: "hid",
|
92
|
+
idle_detection: "idle_detection",
|
98
93
|
magnetometer: "magnetometer",
|
99
94
|
microphone: "microphone",
|
100
95
|
midi: "midi",
|
101
96
|
payment: "payment",
|
102
97
|
picture_in_picture: "picture-in-picture",
|
103
|
-
|
98
|
+
screen_wake_lock: "screen-wake-lock",
|
99
|
+
serial: "serial",
|
100
|
+
sync_xhr: "sync-xhr",
|
104
101
|
usb: "usb",
|
105
|
-
|
106
|
-
vr: "vr",
|
102
|
+
web_share: "web-share",
|
107
103
|
}.freeze
|
108
104
|
|
109
105
|
private_constant :MAPPINGS, :DIRECTIVES
|
@@ -129,6 +125,25 @@ module ActionDispatch # :nodoc:
|
|
129
125
|
end
|
130
126
|
end
|
131
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
|
+
|
132
147
|
def build(context = nil)
|
133
148
|
build_directives(context).compact.join("; ")
|
134
149
|
end
|