actionpack 5.2.1 → 7.0.2.4
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +264 -220
- data/MIT-LICENSE +1 -1
- data/README.rdoc +6 -6
- data/lib/abstract_controller/asset_paths.rb +1 -1
- data/lib/abstract_controller/base.rb +24 -4
- data/lib/abstract_controller/caching/fragments.rb +8 -24
- data/lib/abstract_controller/caching.rb +2 -2
- data/lib/abstract_controller/callbacks.rb +34 -8
- data/lib/abstract_controller/collector.rb +5 -4
- data/lib/abstract_controller/error.rb +1 -1
- data/lib/abstract_controller/helpers.rb +107 -90
- data/lib/abstract_controller/logger.rb +1 -1
- data/lib/abstract_controller/railties/routes_helpers.rb +19 -1
- data/lib/abstract_controller/rendering.rb +9 -9
- data/lib/abstract_controller/translation.rb +12 -5
- data/lib/abstract_controller/url_for.rb +4 -6
- data/lib/abstract_controller.rb +2 -0
- data/lib/action_controller/api.rb +5 -4
- data/lib/action_controller/base.rb +6 -9
- data/lib/action_controller/caching.rb +1 -3
- data/lib/action_controller/log_subscriber.rb +13 -9
- data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
- data/lib/action_controller/metal/conditional_get.rb +57 -6
- data/lib/action_controller/metal/content_security_policy.rb +2 -3
- data/lib/action_controller/metal/cookies.rb +4 -2
- data/lib/action_controller/metal/data_streaming.rb +9 -18
- data/lib/action_controller/metal/default_headers.rb +17 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +4 -6
- data/lib/action_controller/metal/exceptions.rb +55 -12
- data/lib/action_controller/metal/flash.rb +10 -6
- data/lib/action_controller/metal/head.rb +7 -4
- data/lib/action_controller/metal/helpers.rb +15 -6
- data/lib/action_controller/metal/http_authentication.rb +41 -39
- data/lib/action_controller/metal/implicit_render.rb +5 -15
- data/lib/action_controller/metal/instrumentation.rb +59 -55
- data/lib/action_controller/metal/live.rb +80 -33
- data/lib/action_controller/metal/logging.rb +20 -0
- data/lib/action_controller/metal/mime_responds.rb +22 -7
- data/lib/action_controller/metal/parameter_encoding.rb +35 -4
- data/lib/action_controller/metal/params_wrapper.rb +50 -31
- data/lib/action_controller/metal/permissions_policy.rb +46 -0
- data/lib/action_controller/metal/redirecting.rb +93 -23
- data/lib/action_controller/metal/renderers.rb +4 -4
- data/lib/action_controller/metal/rendering.rb +14 -9
- data/lib/action_controller/metal/request_forgery_protection.rb +160 -58
- data/lib/action_controller/metal/rescue.rb +2 -2
- data/lib/action_controller/metal/streaming.rb +1 -4
- data/lib/action_controller/metal/strong_parameters.rb +236 -88
- data/lib/action_controller/metal/testing.rb +9 -2
- data/lib/action_controller/metal/url_for.rb +1 -1
- data/lib/action_controller/metal.rb +16 -17
- data/lib/action_controller/railtie.rb +49 -6
- data/lib/action_controller/railties/helpers.rb +1 -1
- data/lib/action_controller/renderer.rb +37 -13
- data/lib/action_controller/template_assertions.rb +1 -1
- data/lib/action_controller/test_case.rb +98 -68
- data/lib/action_controller.rb +4 -5
- data/lib/action_dispatch/http/cache.rb +45 -32
- data/lib/action_dispatch/http/content_disposition.rb +45 -0
- data/lib/action_dispatch/http/content_security_policy.rb +69 -56
- data/lib/action_dispatch/http/filter_parameters.rb +14 -8
- data/lib/action_dispatch/http/filter_redirect.rb +2 -3
- data/lib/action_dispatch/http/headers.rb +4 -4
- data/lib/action_dispatch/http/mime_negotiation.rb +44 -16
- data/lib/action_dispatch/http/mime_type.rb +47 -30
- data/lib/action_dispatch/http/parameters.rb +18 -27
- data/lib/action_dispatch/http/permissions_policy.rb +173 -0
- data/lib/action_dispatch/http/request.rb +49 -35
- data/lib/action_dispatch/http/response.rb +34 -26
- data/lib/action_dispatch/http/upload.rb +9 -1
- data/lib/action_dispatch/http/url.rb +86 -94
- data/lib/action_dispatch/journey/formatter.rb +55 -31
- data/lib/action_dispatch/journey/gtg/builder.rb +30 -46
- data/lib/action_dispatch/journey/gtg/simulator.rb +15 -8
- data/lib/action_dispatch/journey/gtg/transition_table.rb +78 -21
- data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
- data/lib/action_dispatch/journey/nodes/node.rb +83 -16
- data/lib/action_dispatch/journey/parser.rb +13 -13
- data/lib/action_dispatch/journey/parser.y +1 -1
- data/lib/action_dispatch/journey/path/pattern.rb +42 -34
- data/lib/action_dispatch/journey/route.rb +14 -31
- data/lib/action_dispatch/journey/router/utils.rb +16 -14
- data/lib/action_dispatch/journey/router.rb +27 -35
- data/lib/action_dispatch/journey/routes.rb +3 -5
- data/lib/action_dispatch/journey/scanner.rb +10 -4
- data/lib/action_dispatch/journey/visitors.rb +1 -4
- data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
- data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
- data/lib/action_dispatch/journey.rb +0 -2
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +45 -0
- data/lib/action_dispatch/middleware/callbacks.rb +2 -4
- data/lib/action_dispatch/middleware/cookies.rb +136 -113
- data/lib/action_dispatch/middleware/debug_exceptions.rb +47 -68
- data/lib/action_dispatch/middleware/debug_locks.rb +8 -8
- data/lib/action_dispatch/middleware/debug_view.rb +66 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +79 -30
- data/lib/action_dispatch/middleware/executor.rb +4 -1
- data/lib/action_dispatch/middleware/flash.rb +10 -12
- data/lib/action_dispatch/middleware/host_authorization.rb +159 -0
- data/lib/action_dispatch/middleware/public_exceptions.rb +6 -3
- data/lib/action_dispatch/middleware/remote_ip.rb +30 -20
- data/lib/action_dispatch/middleware/request_id.rb +5 -6
- data/lib/action_dispatch/middleware/server_timing.rb +33 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +16 -3
- data/lib/action_dispatch/middleware/session/cache_store.rb +11 -6
- data/lib/action_dispatch/middleware/session/cookie_store.rb +24 -19
- data/lib/action_dispatch/middleware/show_exceptions.rb +20 -11
- data/lib/action_dispatch/middleware/ssl.rb +20 -15
- data/lib/action_dispatch/middleware/stack.rb +79 -7
- data/lib/action_dispatch/middleware/static.rb +150 -94
- data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
- data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +6 -11
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +4 -2
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +46 -36
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +8 -0
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +7 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +25 -6
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +9 -6
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +4 -1
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +121 -15
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +5 -5
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +5 -5
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +16 -2
- data/lib/action_dispatch/railtie.rb +16 -4
- data/lib/action_dispatch/request/session.rb +59 -22
- data/lib/action_dispatch/request/utils.rb +28 -2
- data/lib/action_dispatch/routing/inspector.rb +102 -54
- data/lib/action_dispatch/routing/mapper.rb +184 -156
- data/lib/action_dispatch/routing/polymorphic_routes.rb +21 -19
- data/lib/action_dispatch/routing/redirection.rb +4 -6
- data/lib/action_dispatch/routing/route_set.rb +83 -73
- data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
- data/lib/action_dispatch/routing/url_for.rb +2 -3
- data/lib/action_dispatch/routing.rb +23 -22
- data/lib/action_dispatch/system_test_case.rb +65 -16
- data/lib/action_dispatch/system_testing/browser.rb +43 -16
- data/lib/action_dispatch/system_testing/driver.rb +42 -10
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +58 -12
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +3 -10
- data/lib/action_dispatch/testing/assertion_response.rb +0 -1
- data/lib/action_dispatch/testing/assertions/response.rb +4 -7
- data/lib/action_dispatch/testing/assertions/routing.rb +20 -8
- data/lib/action_dispatch/testing/assertions.rb +3 -6
- data/lib/action_dispatch/testing/integration.rb +61 -30
- data/lib/action_dispatch/testing/request_encoder.rb +2 -2
- data/lib/action_dispatch/testing/test_process.rb +8 -6
- data/lib/action_dispatch/testing/test_request.rb +3 -3
- data/lib/action_dispatch/testing/test_response.rb +4 -32
- data/lib/action_dispatch.rb +15 -7
- data/lib/action_pack/gem_version.rb +4 -4
- data/lib/action_pack.rb +1 -1
- metadata +44 -25
- data/lib/action_controller/metal/force_ssl.rb +0 -99
- data/lib/action_dispatch/http/parameter_filter.rb +0 -86
- data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
- data/lib/action_dispatch/journey/nfa/simulator.rb +0 -49
- data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -120
- data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +0 -26
@@ -1,34 +1,34 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# -*- frozen-string-literal: true -*-
|
4
|
-
|
5
3
|
require "singleton"
|
6
|
-
require "active_support/core_ext/string/starts_ends_with"
|
7
4
|
|
8
5
|
module Mime
|
9
6
|
class Mimes
|
7
|
+
attr_reader :symbols
|
8
|
+
|
10
9
|
include Enumerable
|
11
10
|
|
12
11
|
def initialize
|
13
12
|
@mimes = []
|
14
|
-
@symbols =
|
13
|
+
@symbols = []
|
15
14
|
end
|
16
15
|
|
17
|
-
def each
|
18
|
-
@mimes.each
|
16
|
+
def each(&block)
|
17
|
+
@mimes.each(&block)
|
19
18
|
end
|
20
19
|
|
21
20
|
def <<(type)
|
22
21
|
@mimes << type
|
23
|
-
@symbols
|
22
|
+
@symbols << type.to_sym
|
24
23
|
end
|
25
24
|
|
26
25
|
def delete_if
|
27
|
-
@mimes.delete_if
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
26
|
+
@mimes.delete_if do |x|
|
27
|
+
if yield x
|
28
|
+
@symbols.delete(x.to_sym)
|
29
|
+
true
|
30
|
+
end
|
31
|
+
end
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
@@ -42,9 +42,9 @@ module Mime
|
|
42
42
|
Type.lookup_by_extension(type)
|
43
43
|
end
|
44
44
|
|
45
|
-
def fetch(type)
|
45
|
+
def fetch(type, &block)
|
46
46
|
return type if type.is_a?(Type)
|
47
|
-
EXTENSION_LOOKUP.fetch(type.to_s)
|
47
|
+
EXTENSION_LOOKUP.fetch(type.to_s, &block)
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
@@ -67,14 +67,14 @@ module Mime
|
|
67
67
|
@register_callbacks = []
|
68
68
|
|
69
69
|
# A simple helper class used in parsing the accept header.
|
70
|
-
class AcceptItem
|
70
|
+
class AcceptItem # :nodoc:
|
71
71
|
attr_accessor :index, :name, :q
|
72
72
|
alias :to_s :name
|
73
73
|
|
74
74
|
def initialize(index, name, q = nil)
|
75
75
|
@index = index
|
76
76
|
@name = name
|
77
|
-
q ||= 0.0 if @name == "*/*"
|
77
|
+
q ||= 0.0 if @name == "*/*" # Default wildcard match to end of list.
|
78
78
|
@q = ((q || 1.0).to_f * 100).to_i
|
79
79
|
end
|
80
80
|
|
@@ -85,7 +85,7 @@ module Mime
|
|
85
85
|
end
|
86
86
|
end
|
87
87
|
|
88
|
-
class AcceptList
|
88
|
+
class AcceptList # :nodoc:
|
89
89
|
def self.sort!(list)
|
90
90
|
list.sort!
|
91
91
|
|
@@ -116,7 +116,7 @@ module Mime
|
|
116
116
|
type = list[idx]
|
117
117
|
break if type.q < app_xml.q
|
118
118
|
|
119
|
-
if type.name.
|
119
|
+
if type.name.end_with? "+xml"
|
120
120
|
list[app_xml_idx], list[idx] = list[idx], app_xml
|
121
121
|
app_xml_idx = idx
|
122
122
|
end
|
@@ -172,6 +172,7 @@ module Mime
|
|
172
172
|
def parse(accept_header)
|
173
173
|
if !accept_header.include?(",")
|
174
174
|
accept_header = accept_header.split(PARAMETER_SEPARATOR_REGEXP).first
|
175
|
+
return [] unless accept_header
|
175
176
|
parse_trailing_star(accept_header) || [Mime::Type.lookup(accept_header)].compact
|
176
177
|
else
|
177
178
|
list, index = [], 0
|
@@ -203,7 +204,7 @@ module Mime
|
|
203
204
|
# For an input of <tt>'application'</tt>, returns <tt>[Mime[:html], Mime[:js],
|
204
205
|
# Mime[:xml], Mime[:yaml], Mime[:atom], Mime[:json], Mime[:rss], Mime[:url_encoded_form]</tt>.
|
205
206
|
def parse_data_with_trailing_star(type)
|
206
|
-
Mime::SET.select { |m| m
|
207
|
+
Mime::SET.select { |m| m.match?(type) }
|
207
208
|
end
|
208
209
|
|
209
210
|
# This method is opposite of register method.
|
@@ -223,7 +224,17 @@ module Mime
|
|
223
224
|
|
224
225
|
attr_reader :hash
|
225
226
|
|
227
|
+
MIME_NAME = "[a-zA-Z0-9][a-zA-Z0-9#{Regexp.escape('!#$&-^_.+')}]{0,126}"
|
228
|
+
MIME_PARAMETER_VALUE = "#{Regexp.escape('"')}?#{MIME_NAME}#{Regexp.escape('"')}?"
|
229
|
+
MIME_PARAMETER = "\s*;\s*#{MIME_NAME}(?:=#{MIME_PARAMETER_VALUE})?"
|
230
|
+
MIME_REGEXP = /\A(?:\*\/\*|#{MIME_NAME}\/(?:\*|#{MIME_NAME})(?>#{MIME_PARAMETER})*\s*)\z/
|
231
|
+
|
232
|
+
class InvalidMimeType < StandardError; end
|
233
|
+
|
226
234
|
def initialize(string, symbol = nil, synonyms = [])
|
235
|
+
unless MIME_REGEXP.match?(string)
|
236
|
+
raise InvalidMimeType, "#{string.inspect} is not a valid MIME type"
|
237
|
+
end
|
227
238
|
@symbol, @synonyms = symbol, synonyms
|
228
239
|
@string = string
|
229
240
|
@hash = [@string, @synonyms, @symbol].hash
|
@@ -273,25 +284,27 @@ module Mime
|
|
273
284
|
@synonyms.any? { |synonym| synonym.to_s =~ regexp } || @string =~ regexp
|
274
285
|
end
|
275
286
|
|
287
|
+
def match?(mime_type)
|
288
|
+
return false unless mime_type
|
289
|
+
regexp = Regexp.new(Regexp.quote(mime_type.to_s))
|
290
|
+
@synonyms.any? { |synonym| synonym.to_s.match?(regexp) } || @string.match?(regexp)
|
291
|
+
end
|
292
|
+
|
276
293
|
def html?
|
277
|
-
symbol == :html || @string
|
294
|
+
(symbol == :html) || /html/.match?(@string)
|
278
295
|
end
|
279
296
|
|
280
297
|
def all?; false; end
|
281
298
|
|
282
|
-
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
283
|
-
# Workaround for Ruby 2.2 "private attribute?" warning.
|
284
299
|
protected
|
285
|
-
|
286
300
|
attr_reader :string, :synonyms
|
287
301
|
|
288
302
|
private
|
289
|
-
|
290
303
|
def to_ary; end
|
291
304
|
def to_a; end
|
292
305
|
|
293
306
|
def method_missing(method, *args)
|
294
|
-
if method.
|
307
|
+
if method.end_with?("?")
|
295
308
|
method[0..-2].downcase.to_sym == to_sym
|
296
309
|
else
|
297
310
|
super
|
@@ -299,7 +312,7 @@ module Mime
|
|
299
312
|
end
|
300
313
|
|
301
314
|
def respond_to_missing?(method, include_private = false)
|
302
|
-
|
315
|
+
method.end_with?("?") || super
|
303
316
|
end
|
304
317
|
end
|
305
318
|
|
@@ -307,7 +320,7 @@ module Mime
|
|
307
320
|
include Singleton
|
308
321
|
|
309
322
|
def initialize
|
310
|
-
super "*/*",
|
323
|
+
super "*/*", nil
|
311
324
|
end
|
312
325
|
|
313
326
|
def all?; true; end
|
@@ -315,7 +328,7 @@ module Mime
|
|
315
328
|
end
|
316
329
|
|
317
330
|
# 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
|
331
|
+
# other concrete types. It's a wildcard match that we use for +respond_to+
|
319
332
|
# negotiation internals.
|
320
333
|
ALL = AllType.instance
|
321
334
|
|
@@ -326,15 +339,19 @@ module Mime
|
|
326
339
|
true
|
327
340
|
end
|
328
341
|
|
342
|
+
def to_s
|
343
|
+
""
|
344
|
+
end
|
345
|
+
|
329
346
|
def ref; end
|
330
347
|
|
331
348
|
private
|
332
349
|
def respond_to_missing?(method, _)
|
333
|
-
method.
|
350
|
+
method.end_with?("?")
|
334
351
|
end
|
335
352
|
|
336
353
|
def method_missing(method, *args)
|
337
|
-
false if method.
|
354
|
+
false if method.end_with?("?")
|
338
355
|
end
|
339
356
|
end
|
340
357
|
end
|
@@ -17,8 +17,8 @@ module ActionDispatch
|
|
17
17
|
# Raised when raw data from the request cannot be parsed by the parser
|
18
18
|
# defined for request's content MIME type.
|
19
19
|
class ParseError < StandardError
|
20
|
-
def initialize
|
21
|
-
super(
|
20
|
+
def initialize(message = $!.message)
|
21
|
+
super(message)
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
@@ -57,16 +57,15 @@ 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
|
64
63
|
alias :params :parameters
|
65
64
|
|
66
|
-
def path_parameters=(parameters)
|
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)
|
@@ -79,30 +78,12 @@ module ActionDispatch
|
|
79
78
|
# Returns a hash with the \parameters used to form the \path of the request.
|
80
79
|
# Returned hash keys are strings:
|
81
80
|
#
|
82
|
-
# {
|
81
|
+
# { action: "my_action", controller: "my_controller" }
|
83
82
|
def path_parameters
|
84
83
|
get_header(PARAMETERS_KEY) || set_header(PARAMETERS_KEY, {})
|
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,10 +92,20 @@ module ActionDispatch
|
|
111
92
|
begin
|
112
93
|
strategy.call(raw_post)
|
113
94
|
rescue # JSON or Ruby code block errors.
|
114
|
-
|
115
|
-
|
95
|
+
log_parse_error_once
|
96
|
+
raise ParseError, "Error occurred while parsing request parameters"
|
97
|
+
end
|
98
|
+
end
|
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:
|
116
106
|
|
117
|
-
|
107
|
+
#{raw_post}
|
108
|
+
MSG
|
118
109
|
end
|
119
110
|
end
|
120
111
|
|
@@ -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
|
@@ -23,6 +23,7 @@ module ActionDispatch
|
|
23
23
|
include ActionDispatch::Http::FilterParameters
|
24
24
|
include ActionDispatch::Http::URL
|
25
25
|
include ActionDispatch::ContentSecurityPolicy::Request
|
26
|
+
include ActionDispatch::PermissionsPolicy::Request
|
26
27
|
include Rack::Request::Env
|
27
28
|
|
28
29
|
autoload :Session, "action_dispatch/request/session"
|
@@ -41,14 +42,14 @@ module ActionDispatch
|
|
41
42
|
HTTP_NEGOTIATE HTTP_PRAGMA HTTP_CLIENT_IP
|
42
43
|
HTTP_X_FORWARDED_FOR HTTP_ORIGIN HTTP_VERSION
|
43
44
|
HTTP_X_CSRF_TOKEN HTTP_X_REQUEST_ID HTTP_X_FORWARDED_HOST
|
44
|
-
SERVER_ADDR
|
45
45
|
].freeze
|
46
46
|
|
47
47
|
ENV_METHODS.each do |env|
|
48
48
|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
|
49
|
-
|
50
|
-
|
51
|
-
|
49
|
+
# frozen_string_literal: true
|
50
|
+
def #{env.delete_prefix("HTTP_").downcase} # def accept_charset
|
51
|
+
get_header "#{env}" # get_header "HTTP_ACCEPT_CHARSET"
|
52
|
+
end # end
|
52
53
|
METHOD
|
53
54
|
end
|
54
55
|
|
@@ -72,7 +73,7 @@ module ActionDispatch
|
|
72
73
|
PASS_NOT_FOUND = Class.new { # :nodoc:
|
73
74
|
def self.action(_); self; end
|
74
75
|
def self.call(_); [404, { "X-Cascade" => "pass" }, []]; end
|
75
|
-
def self.
|
76
|
+
def self.action_encoding_template(action); false; end
|
76
77
|
}
|
77
78
|
|
78
79
|
def controller_class
|
@@ -84,8 +85,16 @@ module ActionDispatch
|
|
84
85
|
def controller_class_for(name)
|
85
86
|
if name
|
86
87
|
controller_param = name.underscore
|
87
|
-
const_name =
|
88
|
-
|
88
|
+
const_name = controller_param.camelize << "Controller"
|
89
|
+
begin
|
90
|
+
const_name.constantize
|
91
|
+
rescue NameError => error
|
92
|
+
if error.missing_name == const_name || const_name.start_with?("#{error.missing_name}::")
|
93
|
+
raise MissingController.new(error.message, error.name)
|
94
|
+
else
|
95
|
+
raise
|
96
|
+
end
|
97
|
+
end
|
89
98
|
else
|
90
99
|
PASS_NOT_FOUND
|
91
100
|
end
|
@@ -125,6 +134,8 @@ module ActionDispatch
|
|
125
134
|
HTTP_METHOD_LOOKUP[method] = method.underscore.to_sym
|
126
135
|
}
|
127
136
|
|
137
|
+
alias raw_request_method request_method # :nodoc:
|
138
|
+
|
128
139
|
# Returns the HTTP \method that the application should see.
|
129
140
|
# In the case where the \method was overridden by a middleware
|
130
141
|
# (for instance, if a HEAD request was converted to a GET,
|
@@ -136,11 +147,11 @@ module ActionDispatch
|
|
136
147
|
end
|
137
148
|
|
138
149
|
def routes # :nodoc:
|
139
|
-
get_header("action_dispatch.routes"
|
150
|
+
get_header("action_dispatch.routes")
|
140
151
|
end
|
141
152
|
|
142
153
|
def routes=(routes) # :nodoc:
|
143
|
-
set_header("action_dispatch.routes"
|
154
|
+
set_header("action_dispatch.routes", routes)
|
144
155
|
end
|
145
156
|
|
146
157
|
def engine_script_name(_routes) # :nodoc:
|
@@ -151,18 +162,18 @@ module ActionDispatch
|
|
151
162
|
set_header(routes.env_key, name.dup)
|
152
163
|
end
|
153
164
|
|
154
|
-
def request_method=(request_method)
|
165
|
+
def request_method=(request_method) # :nodoc:
|
155
166
|
if check_method(request_method)
|
156
167
|
@request_method = set_header("REQUEST_METHOD", request_method)
|
157
168
|
end
|
158
169
|
end
|
159
170
|
|
160
171
|
def controller_instance # :nodoc:
|
161
|
-
get_header("action_controller.instance"
|
172
|
+
get_header("action_controller.instance")
|
162
173
|
end
|
163
174
|
|
164
175
|
def controller_instance=(controller) # :nodoc:
|
165
|
-
set_header("action_controller.instance"
|
176
|
+
set_header("action_controller.instance", controller)
|
166
177
|
end
|
167
178
|
|
168
179
|
def http_auth_salt
|
@@ -173,7 +184,7 @@ module ActionDispatch
|
|
173
184
|
# We're treating `nil` as "unset", and we want the default setting to be
|
174
185
|
# `true`. This logic should be extracted to `env_config` and calculated
|
175
186
|
# once.
|
176
|
-
!(get_header("action_dispatch.show_exceptions"
|
187
|
+
!(get_header("action_dispatch.show_exceptions") == false)
|
177
188
|
end
|
178
189
|
|
179
190
|
# Returns a symbol form of the #request_method.
|
@@ -252,7 +263,7 @@ module ActionDispatch
|
|
252
263
|
# # get "/articles"
|
253
264
|
# request.media_type # => "application/x-www-form-urlencoded"
|
254
265
|
def media_type
|
255
|
-
content_mime_type
|
266
|
+
content_mime_type&.to_s
|
256
267
|
end
|
257
268
|
|
258
269
|
# Returns the content length of the request as an integer.
|
@@ -264,7 +275,7 @@ module ActionDispatch
|
|
264
275
|
# (case-insensitive), which may need to be manually added depending on the
|
265
276
|
# choice of JavaScript libraries and frameworks.
|
266
277
|
def xml_http_request?
|
267
|
-
get_header("HTTP_X_REQUESTED_WITH")
|
278
|
+
/XMLHttpRequest/i.match?(get_header("HTTP_X_REQUESTED_WITH"))
|
268
279
|
end
|
269
280
|
alias :xhr? :xml_http_request?
|
270
281
|
|
@@ -280,10 +291,11 @@ module ActionDispatch
|
|
280
291
|
end
|
281
292
|
|
282
293
|
def remote_ip=(remote_ip)
|
283
|
-
|
294
|
+
@remote_ip = nil
|
295
|
+
set_header "action_dispatch.remote_ip", remote_ip
|
284
296
|
end
|
285
297
|
|
286
|
-
ACTION_DISPATCH_REQUEST_ID = "action_dispatch.request_id"
|
298
|
+
ACTION_DISPATCH_REQUEST_ID = "action_dispatch.request_id" # :nodoc:
|
287
299
|
|
288
300
|
# Returns the unique request id, which is based on either the X-Request-Id header that can
|
289
301
|
# be generated by a firewall, load balancer, or web server or by the RequestId middleware
|
@@ -321,7 +333,7 @@ module ActionDispatch
|
|
321
333
|
# variable is already set, wrap it in a StringIO.
|
322
334
|
def body
|
323
335
|
if raw_post = get_header("RAW_POST_DATA")
|
324
|
-
raw_post = raw_post.
|
336
|
+
raw_post = (+raw_post).force_encoding(Encoding::BINARY)
|
325
337
|
StringIO.new(raw_post)
|
326
338
|
else
|
327
339
|
body_stream
|
@@ -340,21 +352,15 @@ module ActionDispatch
|
|
340
352
|
FORM_DATA_MEDIA_TYPES.include?(media_type)
|
341
353
|
end
|
342
354
|
|
343
|
-
def body_stream
|
355
|
+
def body_stream # :nodoc:
|
344
356
|
get_header("rack.input")
|
345
357
|
end
|
346
358
|
|
347
|
-
# TODO This should be broken apart into AD::Request::Session and probably
|
348
|
-
# be included by the session middleware.
|
349
359
|
def reset_session
|
350
|
-
|
351
|
-
session.destroy
|
352
|
-
else
|
353
|
-
self.session = {}
|
354
|
-
end
|
360
|
+
session.destroy
|
355
361
|
end
|
356
362
|
|
357
|
-
def session=(session)
|
363
|
+
def session=(session) # :nodoc:
|
358
364
|
Session.set self, session
|
359
365
|
end
|
360
366
|
|
@@ -366,6 +372,9 @@ module ActionDispatch
|
|
366
372
|
def GET
|
367
373
|
fetch_header("action_dispatch.request.query_parameters") do |k|
|
368
374
|
rack_query_params = super || {}
|
375
|
+
controller = path_parameters[:controller]
|
376
|
+
action = path_parameters[:action]
|
377
|
+
rack_query_params = Request::Utils.set_binary_encoding(self, rack_query_params, controller, action)
|
369
378
|
# Check for non UTF-8 parameter values, which would cause errors later
|
370
379
|
Request::Utils.check_param_encoding(rack_query_params)
|
371
380
|
set_header k, Request::Utils.normalize_encode_params(rack_query_params)
|
@@ -381,11 +390,10 @@ module ActionDispatch
|
|
381
390
|
pr = parse_formatted_parameters(params_parsers) do |params|
|
382
391
|
super || {}
|
383
392
|
end
|
393
|
+
pr = Request::Utils.set_binary_encoding(self, pr, path_parameters[:controller], path_parameters[:action])
|
394
|
+
Request::Utils.check_param_encoding(pr)
|
384
395
|
self.request_parameters = Request::Utils.normalize_encode_params(pr)
|
385
396
|
end
|
386
|
-
rescue Http::Parameters::ParseError # one of the parse strategies blew up
|
387
|
-
self.request_parameters = Request::Utils.normalize_encode_params(super || {})
|
388
|
-
raise
|
389
397
|
rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
|
390
398
|
raise ActionController::BadRequest.new("Invalid request parameters: #{e.message}")
|
391
399
|
end
|
@@ -402,23 +410,23 @@ module ActionDispatch
|
|
402
410
|
|
403
411
|
# True if the request came from localhost, 127.0.0.1, or ::1.
|
404
412
|
def local?
|
405
|
-
LOCALHOST
|
413
|
+
LOCALHOST.match?(remote_addr) && LOCALHOST.match?(remote_ip)
|
406
414
|
end
|
407
415
|
|
408
416
|
def request_parameters=(params)
|
409
417
|
raise if params.nil?
|
410
|
-
set_header("action_dispatch.request.request_parameters"
|
418
|
+
set_header("action_dispatch.request.request_parameters", params)
|
411
419
|
end
|
412
420
|
|
413
421
|
def logger
|
414
|
-
get_header("action_dispatch.logger"
|
422
|
+
get_header("action_dispatch.logger")
|
415
423
|
end
|
416
424
|
|
417
425
|
def commit_flash
|
418
426
|
end
|
419
427
|
|
420
|
-
def
|
421
|
-
|
428
|
+
def inspect # :nodoc:
|
429
|
+
"#<#{self.class.name} #{method} #{original_url.dump} for #{remote_ip}>"
|
422
430
|
end
|
423
431
|
|
424
432
|
private
|
@@ -426,5 +434,11 @@ module ActionDispatch
|
|
426
434
|
HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS[0...-1].join(', ')}, and #{HTTP_METHODS[-1]}")
|
427
435
|
name
|
428
436
|
end
|
437
|
+
|
438
|
+
def default_session
|
439
|
+
Session.disabled(self)
|
440
|
+
end
|
429
441
|
end
|
430
442
|
end
|
443
|
+
|
444
|
+
ActiveSupport.run_load_hooks :action_dispatch_request, ActionDispatch::Request
|