actionpack 5.2.8.1 → 6.1.6.1
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 +383 -346
- data/MIT-LICENSE +1 -2
- data/README.rdoc +4 -3
- data/lib/abstract_controller/base.rb +38 -4
- data/lib/abstract_controller/caching/fragments.rb +6 -22
- data/lib/abstract_controller/caching.rb +1 -1
- data/lib/abstract_controller/callbacks.rb +14 -2
- data/lib/abstract_controller/collector.rb +5 -4
- data/lib/abstract_controller/helpers.rb +106 -90
- data/lib/abstract_controller/railties/routes_helpers.rb +17 -1
- data/lib/abstract_controller/rendering.rb +9 -9
- data/lib/abstract_controller/translation.rb +11 -5
- data/lib/abstract_controller.rb +1 -0
- data/lib/action_controller/api.rb +4 -3
- data/lib/action_controller/base.rb +6 -9
- data/lib/action_controller/caching.rb +1 -3
- data/lib/action_controller/log_subscriber.rb +10 -7
- data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
- data/lib/action_controller/metal/conditional_get.rb +19 -5
- data/lib/action_controller/metal/content_security_policy.rb +1 -2
- data/lib/action_controller/metal/cookies.rb +3 -1
- data/lib/action_controller/metal/data_streaming.rb +6 -7
- 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 +56 -2
- data/lib/action_controller/metal/flash.rb +5 -5
- data/lib/action_controller/metal/head.rb +7 -4
- data/lib/action_controller/metal/helpers.rb +14 -5
- data/lib/action_controller/metal/http_authentication.rb +25 -23
- data/lib/action_controller/metal/implicit_render.rb +5 -15
- data/lib/action_controller/metal/instrumentation.rb +13 -14
- data/lib/action_controller/metal/live.rb +39 -32
- data/lib/action_controller/metal/logging.rb +20 -0
- data/lib/action_controller/metal/mime_responds.rb +19 -4
- data/lib/action_controller/metal/parameter_encoding.rb +35 -4
- data/lib/action_controller/metal/params_wrapper.rb +32 -22
- data/lib/action_controller/metal/permissions_policy.rb +46 -0
- data/lib/action_controller/metal/redirecting.rb +6 -6
- data/lib/action_controller/metal/renderers.rb +4 -4
- data/lib/action_controller/metal/rendering.rb +8 -3
- data/lib/action_controller/metal/request_forgery_protection.rb +26 -49
- data/lib/action_controller/metal/rescue.rb +1 -1
- data/lib/action_controller/metal/streaming.rb +0 -1
- data/lib/action_controller/metal/strong_parameters.rb +168 -59
- data/lib/action_controller/metal/url_for.rb +1 -1
- data/lib/action_controller/metal.rb +10 -8
- 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 +71 -63
- data/lib/action_controller.rb +7 -4
- data/lib/action_dispatch/http/cache.rb +31 -27
- data/lib/action_dispatch/http/content_disposition.rb +45 -0
- data/lib/action_dispatch/http/content_security_policy.rb +34 -18
- data/lib/action_dispatch/http/filter_parameters.rb +9 -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 +26 -13
- data/lib/action_dispatch/http/mime_type.rb +43 -24
- data/lib/action_dispatch/http/parameters.rb +14 -23
- data/lib/action_dispatch/http/permissions_policy.rb +173 -0
- data/lib/action_dispatch/http/request.rb +45 -22
- data/lib/action_dispatch/http/response.rb +45 -25
- data/lib/action_dispatch/http/upload.rb +9 -1
- data/lib/action_dispatch/http/url.rb +82 -82
- data/lib/action_dispatch/journey/formatter.rb +55 -31
- data/lib/action_dispatch/journey/gtg/builder.rb +22 -37
- data/lib/action_dispatch/journey/gtg/simulator.rb +8 -7
- data/lib/action_dispatch/journey/gtg/transition_table.rb +6 -5
- data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
- data/lib/action_dispatch/journey/nodes/node.rb +13 -11
- 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 +19 -21
- data/lib/action_dispatch/journey/route.rb +10 -20
- data/lib/action_dispatch/journey/router/utils.rb +14 -12
- data/lib/action_dispatch/journey/router.rb +26 -34
- data/lib/action_dispatch/journey/routes.rb +0 -2
- data/lib/action_dispatch/journey/scanner.rb +10 -4
- data/lib/action_dispatch/journey/visitors.rb +1 -4
- data/lib/action_dispatch/journey.rb +0 -2
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
- data/lib/action_dispatch/middleware/callbacks.rb +2 -4
- data/lib/action_dispatch/middleware/cookies.rb +128 -109
- data/lib/action_dispatch/middleware/debug_exceptions.rb +43 -66
- data/lib/action_dispatch/middleware/debug_locks.rb +5 -5
- data/lib/action_dispatch/middleware/debug_view.rb +66 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +75 -30
- data/lib/action_dispatch/middleware/flash.rb +1 -1
- data/lib/action_dispatch/middleware/host_authorization.rb +170 -0
- data/lib/action_dispatch/middleware/public_exceptions.rb +6 -3
- data/lib/action_dispatch/middleware/remote_ip.rb +14 -16
- data/lib/action_dispatch/middleware/request_id.rb +5 -6
- data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -3
- data/lib/action_dispatch/middleware/session/cookie_store.rb +3 -9
- data/lib/action_dispatch/middleware/show_exceptions.rb +13 -2
- data/lib/action_dispatch/middleware/ssl.rb +20 -15
- data/lib/action_dispatch/middleware/stack.rb +56 -2
- data/lib/action_dispatch/middleware/static.rb +153 -93
- 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 +3 -1
- 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 +45 -35
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -0
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +23 -4
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +6 -3
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +4 -1
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +104 -8
- 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 +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +24 -1
- data/lib/action_dispatch/railtie.rb +8 -2
- data/lib/action_dispatch/request/session.rb +11 -10
- data/lib/action_dispatch/request/utils.rb +26 -2
- data/lib/action_dispatch/routing/inspector.rb +100 -52
- data/lib/action_dispatch/routing/mapper.rb +155 -103
- data/lib/action_dispatch/routing/polymorphic_routes.rb +13 -15
- data/lib/action_dispatch/routing/redirection.rb +4 -4
- data/lib/action_dispatch/routing/route_set.rb +71 -69
- data/lib/action_dispatch/routing/url_for.rb +2 -2
- data/lib/action_dispatch/routing.rb +21 -20
- data/lib/action_dispatch/system_test_case.rb +60 -11
- data/lib/action_dispatch/system_testing/browser.rb +53 -16
- data/lib/action_dispatch/system_testing/driver.rb +11 -3
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +49 -7
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +8 -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 +1 -1
- data/lib/action_dispatch/testing/integration.rb +60 -28
- data/lib/action_dispatch/testing/request_encoder.rb +2 -2
- data/lib/action_dispatch/testing/test_process.rb +32 -4
- 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 +9 -3
- data/lib/action_pack/gem_version.rb +3 -3
- data/lib/action_pack.rb +1 -1
- metadata +34 -21
- 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
@@ -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]"
|
@@ -23,13 +23,13 @@ module ActionDispatch
|
|
23
23
|
# change { file: { code: "xxxx"} }
|
24
24
|
#
|
25
25
|
# env["action_dispatch.parameter_filter"] = -> (k, v) do
|
26
|
-
# v.reverse! if k
|
26
|
+
# v.reverse! if k.match?(/secret/i)
|
27
27
|
# end
|
28
28
|
# => reverses the value to all keys matching /secret/i
|
29
29
|
module FilterParameters
|
30
30
|
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:
|
31
|
+
NULL_PARAM_FILTER = ActiveSupport::ParameterFilter.new # :nodoc:
|
32
|
+
NULL_ENV_FILTER = ActiveSupport::ParameterFilter.new ENV_MATCH # :nodoc:
|
33
33
|
|
34
34
|
def initialize
|
35
35
|
super
|
@@ -41,6 +41,8 @@ module ActionDispatch
|
|
41
41
|
# Returns a hash of parameters with all sensitive data replaced.
|
42
42
|
def filtered_parameters
|
43
43
|
@filtered_parameters ||= parameter_filter.filter(parameters)
|
44
|
+
rescue ActionDispatch::Http::Parameters::ParseError
|
45
|
+
@filtered_parameters = {}
|
44
46
|
end
|
45
47
|
|
46
48
|
# Returns a hash of request.env with all sensitive data replaced.
|
@@ -54,7 +56,6 @@ module ActionDispatch
|
|
54
56
|
end
|
55
57
|
|
56
58
|
private
|
57
|
-
|
58
59
|
def parameter_filter # :doc:
|
59
60
|
parameter_filter_for fetch_header("action_dispatch.parameter_filter") {
|
60
61
|
return NULL_PARAM_FILTER
|
@@ -69,7 +70,7 @@ module ActionDispatch
|
|
69
70
|
end
|
70
71
|
|
71
72
|
def parameter_filter_for(filters) # :doc:
|
72
|
-
ParameterFilter.new(filters)
|
73
|
+
ActiveSupport::ParameterFilter.new(filters)
|
73
74
|
end
|
74
75
|
|
75
76
|
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,6 +7,13 @@ 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
|
12
19
|
end
|
@@ -20,6 +27,8 @@ module ActionDispatch
|
|
20
27
|
nil
|
21
28
|
end
|
22
29
|
set_header k, v
|
30
|
+
rescue ::Mime::Type::InvalidMimeType => e
|
31
|
+
raise InvalidType, e.message
|
23
32
|
end
|
24
33
|
end
|
25
34
|
|
@@ -42,6 +51,8 @@ module ActionDispatch
|
|
42
51
|
Mime::Type.parse(header)
|
43
52
|
end
|
44
53
|
set_header k, v
|
54
|
+
rescue ::Mime::Type::InvalidMimeType => e
|
55
|
+
raise InvalidType, e.message
|
45
56
|
end
|
46
57
|
end
|
47
58
|
|
@@ -57,13 +68,7 @@ module ActionDispatch
|
|
57
68
|
|
58
69
|
def formats
|
59
70
|
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
|
71
|
+
v = if params_readable?
|
67
72
|
Array(Mime[parameters[:format]])
|
68
73
|
elsif use_accept_header && valid_accept_header
|
69
74
|
accepts
|
@@ -90,10 +95,7 @@ module ActionDispatch
|
|
90
95
|
if variant.all? { |v| v.is_a?(Symbol) }
|
91
96
|
@variant = ActiveSupport::ArrayInquirer.new(variant)
|
92
97
|
else
|
93
|
-
raise ArgumentError, "request.variant must be set to a Symbol or an Array of Symbols.
|
94
|
-
"For security reasons, never directly set the variant to a user-provided value, " \
|
95
|
-
"like params[:variant].to_sym. Check user-provided value against a whitelist first, " \
|
96
|
-
"then set the variant: request.variant = :tablet if params[:variant] == 'tablet'"
|
98
|
+
raise ArgumentError, "request.variant must be set to a Symbol or an Array of Symbols."
|
97
99
|
end
|
98
100
|
end
|
99
101
|
|
@@ -151,13 +153,24 @@ module ActionDispatch
|
|
151
153
|
order.include?(Mime::ALL) ? format : nil
|
152
154
|
end
|
153
155
|
|
154
|
-
|
156
|
+
def should_apply_vary_header?
|
157
|
+
!params_readable? && use_accept_header && valid_accept_header
|
158
|
+
end
|
155
159
|
|
160
|
+
private
|
161
|
+
# We use normal content negotiation unless you include */* in your list,
|
162
|
+
# in which case we assume you're a browser and send HTML.
|
156
163
|
BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/
|
157
164
|
|
165
|
+
def params_readable? # :doc:
|
166
|
+
parameters[:format]
|
167
|
+
rescue *RESCUABLE_MIME_FORMAT_ERRORS
|
168
|
+
false
|
169
|
+
end
|
170
|
+
|
158
171
|
def valid_accept_header # :doc:
|
159
172
|
(xhr? && (accept.present? || content_mime_type)) ||
|
160
|
-
(accept.present? && accept
|
173
|
+
(accept.present? && !accept.match?(BROWSER_LIKE_ACCEPTS))
|
161
174
|
end
|
162
175
|
|
163
176
|
def use_accept_header # :doc:
|
@@ -1,17 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# -*- frozen-string-literal: true -*-
|
4
|
-
|
5
3
|
require "singleton"
|
6
|
-
require "active_support/core_ext/
|
4
|
+
require "active_support/core_ext/symbol/starts_ends_with"
|
7
5
|
|
8
6
|
module Mime
|
9
7
|
class Mimes
|
8
|
+
attr_reader :symbols
|
9
|
+
|
10
10
|
include Enumerable
|
11
11
|
|
12
12
|
def initialize
|
13
13
|
@mimes = []
|
14
|
-
@symbols =
|
14
|
+
@symbols = []
|
15
15
|
end
|
16
16
|
|
17
17
|
def each
|
@@ -20,15 +20,16 @@ module Mime
|
|
20
20
|
|
21
21
|
def <<(type)
|
22
22
|
@mimes << type
|
23
|
-
@symbols
|
23
|
+
@symbols << type.to_sym
|
24
24
|
end
|
25
25
|
|
26
26
|
def delete_if
|
27
|
-
@mimes.delete_if
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
27
|
+
@mimes.delete_if do |x|
|
28
|
+
if yield x
|
29
|
+
@symbols.delete(x.to_sym)
|
30
|
+
true
|
31
|
+
end
|
32
|
+
end
|
32
33
|
end
|
33
34
|
end
|
34
35
|
|
@@ -74,7 +75,7 @@ module Mime
|
|
74
75
|
def initialize(index, name, q = nil)
|
75
76
|
@index = index
|
76
77
|
@name = name
|
77
|
-
q ||= 0.0 if @name == "*/*"
|
78
|
+
q ||= 0.0 if @name == "*/*" # Default wildcard match to end of list.
|
78
79
|
@q = ((q || 1.0).to_f * 100).to_i
|
79
80
|
end
|
80
81
|
|
@@ -116,7 +117,7 @@ module Mime
|
|
116
117
|
type = list[idx]
|
117
118
|
break if type.q < app_xml.q
|
118
119
|
|
119
|
-
if type.name.
|
120
|
+
if type.name.end_with? "+xml"
|
120
121
|
list[app_xml_idx], list[idx] = list[idx], app_xml
|
121
122
|
app_xml_idx = idx
|
122
123
|
end
|
@@ -172,6 +173,7 @@ module Mime
|
|
172
173
|
def parse(accept_header)
|
173
174
|
if !accept_header.include?(",")
|
174
175
|
accept_header = accept_header.split(PARAMETER_SEPARATOR_REGEXP).first
|
176
|
+
return [] unless accept_header
|
175
177
|
parse_trailing_star(accept_header) || [Mime::Type.lookup(accept_header)].compact
|
176
178
|
else
|
177
179
|
list, index = [], 0
|
@@ -203,7 +205,7 @@ module Mime
|
|
203
205
|
# For an input of <tt>'application'</tt>, returns <tt>[Mime[:html], Mime[:js],
|
204
206
|
# Mime[:xml], Mime[:yaml], Mime[:atom], Mime[:json], Mime[:rss], Mime[:url_encoded_form]</tt>.
|
205
207
|
def parse_data_with_trailing_star(type)
|
206
|
-
Mime::SET.select { |m| m
|
208
|
+
Mime::SET.select { |m| m.match?(type) }
|
207
209
|
end
|
208
210
|
|
209
211
|
# This method is opposite of register method.
|
@@ -223,7 +225,18 @@ module Mime
|
|
223
225
|
|
224
226
|
attr_reader :hash
|
225
227
|
|
228
|
+
MIME_NAME = "[a-zA-Z0-9][a-zA-Z0-9#{Regexp.escape('!#$&-^_.+')}]{0,126}"
|
229
|
+
MIME_PARAMETER_KEY = "[a-zA-Z0-9][a-zA-Z0-9#{Regexp.escape('!#$&-^_.+')}]{0,126}"
|
230
|
+
MIME_PARAMETER_VALUE = "#{Regexp.escape('"')}?[a-zA-Z0-9][a-zA-Z0-9#{Regexp.escape('!#$&-^_.+')}]{0,126}#{Regexp.escape('"')}?"
|
231
|
+
MIME_PARAMETER = "\s*\;\s*#{MIME_PARAMETER_KEY}(?:\=#{MIME_PARAMETER_VALUE})?"
|
232
|
+
MIME_REGEXP = /\A(?:\*\/\*|#{MIME_NAME}\/(?:\*|#{MIME_NAME})(?>\s*#{MIME_PARAMETER}\s*)*)\z/
|
233
|
+
|
234
|
+
class InvalidMimeType < StandardError; end
|
235
|
+
|
226
236
|
def initialize(string, symbol = nil, synonyms = [])
|
237
|
+
unless MIME_REGEXP.match?(string)
|
238
|
+
raise InvalidMimeType, "#{string.inspect} is not a valid MIME type"
|
239
|
+
end
|
227
240
|
@symbol, @synonyms = symbol, synonyms
|
228
241
|
@string = string
|
229
242
|
@hash = [@string, @synonyms, @symbol].hash
|
@@ -273,25 +286,27 @@ module Mime
|
|
273
286
|
@synonyms.any? { |synonym| synonym.to_s =~ regexp } || @string =~ regexp
|
274
287
|
end
|
275
288
|
|
289
|
+
def match?(mime_type)
|
290
|
+
return false unless mime_type
|
291
|
+
regexp = Regexp.new(Regexp.quote(mime_type.to_s))
|
292
|
+
@synonyms.any? { |synonym| synonym.to_s.match?(regexp) } || @string.match?(regexp)
|
293
|
+
end
|
294
|
+
|
276
295
|
def html?
|
277
|
-
symbol == :html || @string
|
296
|
+
(symbol == :html) || /html/.match?(@string)
|
278
297
|
end
|
279
298
|
|
280
299
|
def all?; false; end
|
281
300
|
|
282
|
-
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
283
|
-
# Workaround for Ruby 2.2 "private attribute?" warning.
|
284
301
|
protected
|
285
|
-
|
286
302
|
attr_reader :string, :synonyms
|
287
303
|
|
288
304
|
private
|
289
|
-
|
290
305
|
def to_ary; end
|
291
306
|
def to_a; end
|
292
307
|
|
293
308
|
def method_missing(method, *args)
|
294
|
-
if method.
|
309
|
+
if method.end_with?("?")
|
295
310
|
method[0..-2].downcase.to_sym == to_sym
|
296
311
|
else
|
297
312
|
super
|
@@ -299,7 +314,7 @@ module Mime
|
|
299
314
|
end
|
300
315
|
|
301
316
|
def respond_to_missing?(method, include_private = false)
|
302
|
-
|
317
|
+
method.end_with?("?") || super
|
303
318
|
end
|
304
319
|
end
|
305
320
|
|
@@ -307,7 +322,7 @@ module Mime
|
|
307
322
|
include Singleton
|
308
323
|
|
309
324
|
def initialize
|
310
|
-
super "*/*",
|
325
|
+
super "*/*", nil
|
311
326
|
end
|
312
327
|
|
313
328
|
def all?; true; end
|
@@ -315,7 +330,7 @@ module Mime
|
|
315
330
|
end
|
316
331
|
|
317
332
|
# ALL isn't a real MIME type, so we don't register it for lookup with the
|
318
|
-
# other concrete types. It's a wildcard match that we use for
|
333
|
+
# other concrete types. It's a wildcard match that we use for +respond_to+
|
319
334
|
# negotiation internals.
|
320
335
|
ALL = AllType.instance
|
321
336
|
|
@@ -326,15 +341,19 @@ module Mime
|
|
326
341
|
true
|
327
342
|
end
|
328
343
|
|
344
|
+
def to_s
|
345
|
+
""
|
346
|
+
end
|
347
|
+
|
329
348
|
def ref; end
|
330
349
|
|
331
350
|
private
|
332
351
|
def respond_to_missing?(method, _)
|
333
|
-
method.
|
352
|
+
method.end_with?("?")
|
334
353
|
end
|
335
354
|
|
336
355
|
def method_missing(method, *args)
|
337
|
-
false if method.
|
356
|
+
false if method.end_with?("?")
|
338
357
|
end
|
339
358
|
end
|
340
359
|
end
|
@@ -57,7 +57,6 @@ module ActionDispatch
|
|
57
57
|
query_parameters.dup
|
58
58
|
end
|
59
59
|
params.merge!(path_parameters)
|
60
|
-
params = set_binary_encoding(params, params[:controller], params[:action])
|
61
60
|
set_header("action_dispatch.request.parameters", params)
|
62
61
|
params
|
63
62
|
end
|
@@ -66,7 +65,7 @@ module ActionDispatch
|
|
66
65
|
def path_parameters=(parameters) #:nodoc:
|
67
66
|
delete_header("action_dispatch.request.parameters")
|
68
67
|
|
69
|
-
parameters = set_binary_encoding(parameters, parameters[:controller], parameters[:action])
|
68
|
+
parameters = Request::Utils.set_binary_encoding(self, parameters, parameters[:controller], parameters[:action])
|
70
69
|
# If any of the path parameters has an invalid encoding then
|
71
70
|
# raise since it's likely to trigger errors further on.
|
72
71
|
Request::Utils.check_param_encoding(parameters)
|
@@ -85,24 +84,6 @@ module ActionDispatch
|
|
85
84
|
end
|
86
85
|
|
87
86
|
private
|
88
|
-
|
89
|
-
def set_binary_encoding(params, controller, action)
|
90
|
-
return params unless controller && controller.valid_encoding?
|
91
|
-
|
92
|
-
if binary_params_for?(controller, action)
|
93
|
-
ActionDispatch::Request::Utils.each_param_value(params) do |param|
|
94
|
-
param.force_encoding ::Encoding::ASCII_8BIT
|
95
|
-
end
|
96
|
-
end
|
97
|
-
params
|
98
|
-
end
|
99
|
-
|
100
|
-
def binary_params_for?(controller, action)
|
101
|
-
controller_class_for(controller).binary_params_for?(action)
|
102
|
-
rescue NameError
|
103
|
-
false
|
104
|
-
end
|
105
|
-
|
106
87
|
def parse_formatted_parameters(parsers)
|
107
88
|
return yield if content_length.zero? || content_mime_type.nil?
|
108
89
|
|
@@ -111,13 +92,23 @@ module ActionDispatch
|
|
111
92
|
begin
|
112
93
|
strategy.call(raw_post)
|
113
94
|
rescue # JSON or Ruby code block errors.
|
114
|
-
|
115
|
-
my_logger.debug "Error occurred while parsing request parameters.\nContents:\n\n#{raw_post}"
|
116
|
-
|
95
|
+
log_parse_error_once
|
117
96
|
raise ParseError
|
118
97
|
end
|
119
98
|
end
|
120
99
|
|
100
|
+
def log_parse_error_once
|
101
|
+
@parse_error_logged ||= begin
|
102
|
+
parse_logger = logger || ActiveSupport::Logger.new($stderr)
|
103
|
+
parse_logger.debug <<~MSG.chomp
|
104
|
+
Error occurred while parsing request parameters.
|
105
|
+
Contents:
|
106
|
+
|
107
|
+
#{raw_post}
|
108
|
+
MSG
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
121
112
|
def params_parsers
|
122
113
|
ActionDispatch::Request.parameter_parsers
|
123
114
|
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/object/deep_dup"
|
4
|
+
|
5
|
+
module ActionDispatch #:nodoc:
|
6
|
+
class PermissionsPolicy
|
7
|
+
class Middleware
|
8
|
+
CONTENT_TYPE = "Content-Type"
|
9
|
+
# The Feature-Policy header has been renamed to Permissions-Policy.
|
10
|
+
# The Permissions-Policy requires a different implementation and isn't
|
11
|
+
# yet supported by all browsers. To avoid having to rename this
|
12
|
+
# middleware in the future we use the new name for the middleware but
|
13
|
+
# keep the old header name and implementation for now.
|
14
|
+
POLICY = "Feature-Policy"
|
15
|
+
|
16
|
+
def initialize(app)
|
17
|
+
@app = app
|
18
|
+
end
|
19
|
+
|
20
|
+
def call(env)
|
21
|
+
request = ActionDispatch::Request.new(env)
|
22
|
+
_, headers, _ = response = @app.call(env)
|
23
|
+
|
24
|
+
return response unless html_response?(headers)
|
25
|
+
return response if policy_present?(headers)
|
26
|
+
|
27
|
+
if policy = request.permissions_policy
|
28
|
+
headers[POLICY] = policy.build(request.controller_instance)
|
29
|
+
end
|
30
|
+
|
31
|
+
if policy_empty?(policy)
|
32
|
+
headers.delete(POLICY)
|
33
|
+
end
|
34
|
+
|
35
|
+
response
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def html_response?(headers)
|
40
|
+
if content_type = headers[CONTENT_TYPE]
|
41
|
+
/html/.match?(content_type)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def policy_present?(headers)
|
46
|
+
headers[POLICY]
|
47
|
+
end
|
48
|
+
|
49
|
+
def policy_empty?(policy)
|
50
|
+
policy&.directives&.empty?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
module Request
|
55
|
+
POLICY = "action_dispatch.permissions_policy"
|
56
|
+
|
57
|
+
def permissions_policy
|
58
|
+
get_header(POLICY)
|
59
|
+
end
|
60
|
+
|
61
|
+
def permissions_policy=(policy)
|
62
|
+
set_header(POLICY, policy)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
MAPPINGS = {
|
67
|
+
self: "'self'",
|
68
|
+
none: "'none'",
|
69
|
+
}.freeze
|
70
|
+
|
71
|
+
# List of available permissions can be found at
|
72
|
+
# https://github.com/w3c/webappsec-permissions-policy/blob/master/features.md#policy-controlled-features
|
73
|
+
DIRECTIVES = {
|
74
|
+
accelerometer: "accelerometer",
|
75
|
+
ambient_light_sensor: "ambient-light-sensor",
|
76
|
+
autoplay: "autoplay",
|
77
|
+
camera: "camera",
|
78
|
+
encrypted_media: "encrypted-media",
|
79
|
+
fullscreen: "fullscreen",
|
80
|
+
geolocation: "geolocation",
|
81
|
+
gyroscope: "gyroscope",
|
82
|
+
magnetometer: "magnetometer",
|
83
|
+
microphone: "microphone",
|
84
|
+
midi: "midi",
|
85
|
+
payment: "payment",
|
86
|
+
picture_in_picture: "picture-in-picture",
|
87
|
+
speaker: "speaker",
|
88
|
+
usb: "usb",
|
89
|
+
vibrate: "vibrate",
|
90
|
+
vr: "vr",
|
91
|
+
}.freeze
|
92
|
+
|
93
|
+
private_constant :MAPPINGS, :DIRECTIVES
|
94
|
+
|
95
|
+
attr_reader :directives
|
96
|
+
|
97
|
+
def initialize
|
98
|
+
@directives = {}
|
99
|
+
yield self if block_given?
|
100
|
+
end
|
101
|
+
|
102
|
+
def initialize_copy(other)
|
103
|
+
@directives = other.directives.deep_dup
|
104
|
+
end
|
105
|
+
|
106
|
+
DIRECTIVES.each do |name, directive|
|
107
|
+
define_method(name) do |*sources|
|
108
|
+
if sources.first
|
109
|
+
@directives[directive] = apply_mappings(sources)
|
110
|
+
else
|
111
|
+
@directives.delete(directive)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def build(context = nil)
|
117
|
+
build_directives(context).compact.join("; ")
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
def apply_mappings(sources)
|
122
|
+
sources.map do |source|
|
123
|
+
case source
|
124
|
+
when Symbol
|
125
|
+
apply_mapping(source)
|
126
|
+
when String, Proc
|
127
|
+
source
|
128
|
+
else
|
129
|
+
raise ArgumentError, "Invalid HTTP permissions policy source: #{source.inspect}"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def apply_mapping(source)
|
135
|
+
MAPPINGS.fetch(source) do
|
136
|
+
raise ArgumentError, "Unknown HTTP permissions policy source mapping: #{source.inspect}"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def build_directives(context)
|
141
|
+
@directives.map do |directive, sources|
|
142
|
+
if sources.is_a?(Array)
|
143
|
+
"#{directive} #{build_directive(sources, context).join(' ')}"
|
144
|
+
elsif sources
|
145
|
+
directive
|
146
|
+
else
|
147
|
+
nil
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def build_directive(sources, context)
|
153
|
+
sources.map { |source| resolve_source(source, context) }
|
154
|
+
end
|
155
|
+
|
156
|
+
def resolve_source(source, context)
|
157
|
+
case source
|
158
|
+
when String
|
159
|
+
source
|
160
|
+
when Symbol
|
161
|
+
source.to_s
|
162
|
+
when Proc
|
163
|
+
if context.nil?
|
164
|
+
raise RuntimeError, "Missing context for the dynamic permissions policy source: #{source.inspect}"
|
165
|
+
else
|
166
|
+
context.instance_exec(&source)
|
167
|
+
end
|
168
|
+
else
|
169
|
+
raise RuntimeError, "Unexpected permissions policy source: #{source.inspect}"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|