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.

Files changed (167) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +264 -220
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -6
  5. data/lib/abstract_controller/asset_paths.rb +1 -1
  6. data/lib/abstract_controller/base.rb +24 -4
  7. data/lib/abstract_controller/caching/fragments.rb +8 -24
  8. data/lib/abstract_controller/caching.rb +2 -2
  9. data/lib/abstract_controller/callbacks.rb +34 -8
  10. data/lib/abstract_controller/collector.rb +5 -4
  11. data/lib/abstract_controller/error.rb +1 -1
  12. data/lib/abstract_controller/helpers.rb +107 -90
  13. data/lib/abstract_controller/logger.rb +1 -1
  14. data/lib/abstract_controller/railties/routes_helpers.rb +19 -1
  15. data/lib/abstract_controller/rendering.rb +9 -9
  16. data/lib/abstract_controller/translation.rb +12 -5
  17. data/lib/abstract_controller/url_for.rb +4 -6
  18. data/lib/abstract_controller.rb +2 -0
  19. data/lib/action_controller/api.rb +5 -4
  20. data/lib/action_controller/base.rb +6 -9
  21. data/lib/action_controller/caching.rb +1 -3
  22. data/lib/action_controller/log_subscriber.rb +13 -9
  23. data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
  24. data/lib/action_controller/metal/conditional_get.rb +57 -6
  25. data/lib/action_controller/metal/content_security_policy.rb +2 -3
  26. data/lib/action_controller/metal/cookies.rb +4 -2
  27. data/lib/action_controller/metal/data_streaming.rb +9 -18
  28. data/lib/action_controller/metal/default_headers.rb +17 -0
  29. data/lib/action_controller/metal/etag_with_template_digest.rb +4 -6
  30. data/lib/action_controller/metal/exceptions.rb +55 -12
  31. data/lib/action_controller/metal/flash.rb +10 -6
  32. data/lib/action_controller/metal/head.rb +7 -4
  33. data/lib/action_controller/metal/helpers.rb +15 -6
  34. data/lib/action_controller/metal/http_authentication.rb +41 -39
  35. data/lib/action_controller/metal/implicit_render.rb +5 -15
  36. data/lib/action_controller/metal/instrumentation.rb +59 -55
  37. data/lib/action_controller/metal/live.rb +80 -33
  38. data/lib/action_controller/metal/logging.rb +20 -0
  39. data/lib/action_controller/metal/mime_responds.rb +22 -7
  40. data/lib/action_controller/metal/parameter_encoding.rb +35 -4
  41. data/lib/action_controller/metal/params_wrapper.rb +50 -31
  42. data/lib/action_controller/metal/permissions_policy.rb +46 -0
  43. data/lib/action_controller/metal/redirecting.rb +93 -23
  44. data/lib/action_controller/metal/renderers.rb +4 -4
  45. data/lib/action_controller/metal/rendering.rb +14 -9
  46. data/lib/action_controller/metal/request_forgery_protection.rb +160 -58
  47. data/lib/action_controller/metal/rescue.rb +2 -2
  48. data/lib/action_controller/metal/streaming.rb +1 -4
  49. data/lib/action_controller/metal/strong_parameters.rb +236 -88
  50. data/lib/action_controller/metal/testing.rb +9 -2
  51. data/lib/action_controller/metal/url_for.rb +1 -1
  52. data/lib/action_controller/metal.rb +16 -17
  53. data/lib/action_controller/railtie.rb +49 -6
  54. data/lib/action_controller/railties/helpers.rb +1 -1
  55. data/lib/action_controller/renderer.rb +37 -13
  56. data/lib/action_controller/template_assertions.rb +1 -1
  57. data/lib/action_controller/test_case.rb +98 -68
  58. data/lib/action_controller.rb +4 -5
  59. data/lib/action_dispatch/http/cache.rb +45 -32
  60. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  61. data/lib/action_dispatch/http/content_security_policy.rb +69 -56
  62. data/lib/action_dispatch/http/filter_parameters.rb +14 -8
  63. data/lib/action_dispatch/http/filter_redirect.rb +2 -3
  64. data/lib/action_dispatch/http/headers.rb +4 -4
  65. data/lib/action_dispatch/http/mime_negotiation.rb +44 -16
  66. data/lib/action_dispatch/http/mime_type.rb +47 -30
  67. data/lib/action_dispatch/http/parameters.rb +18 -27
  68. data/lib/action_dispatch/http/permissions_policy.rb +173 -0
  69. data/lib/action_dispatch/http/request.rb +49 -35
  70. data/lib/action_dispatch/http/response.rb +34 -26
  71. data/lib/action_dispatch/http/upload.rb +9 -1
  72. data/lib/action_dispatch/http/url.rb +86 -94
  73. data/lib/action_dispatch/journey/formatter.rb +55 -31
  74. data/lib/action_dispatch/journey/gtg/builder.rb +30 -46
  75. data/lib/action_dispatch/journey/gtg/simulator.rb +15 -8
  76. data/lib/action_dispatch/journey/gtg/transition_table.rb +78 -21
  77. data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
  78. data/lib/action_dispatch/journey/nodes/node.rb +83 -16
  79. data/lib/action_dispatch/journey/parser.rb +13 -13
  80. data/lib/action_dispatch/journey/parser.y +1 -1
  81. data/lib/action_dispatch/journey/path/pattern.rb +42 -34
  82. data/lib/action_dispatch/journey/route.rb +14 -31
  83. data/lib/action_dispatch/journey/router/utils.rb +16 -14
  84. data/lib/action_dispatch/journey/router.rb +27 -35
  85. data/lib/action_dispatch/journey/routes.rb +3 -5
  86. data/lib/action_dispatch/journey/scanner.rb +10 -4
  87. data/lib/action_dispatch/journey/visitors.rb +1 -4
  88. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  89. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  90. data/lib/action_dispatch/journey.rb +0 -2
  91. data/lib/action_dispatch/middleware/actionable_exceptions.rb +45 -0
  92. data/lib/action_dispatch/middleware/callbacks.rb +2 -4
  93. data/lib/action_dispatch/middleware/cookies.rb +136 -113
  94. data/lib/action_dispatch/middleware/debug_exceptions.rb +47 -68
  95. data/lib/action_dispatch/middleware/debug_locks.rb +8 -8
  96. data/lib/action_dispatch/middleware/debug_view.rb +66 -0
  97. data/lib/action_dispatch/middleware/exception_wrapper.rb +79 -30
  98. data/lib/action_dispatch/middleware/executor.rb +4 -1
  99. data/lib/action_dispatch/middleware/flash.rb +10 -12
  100. data/lib/action_dispatch/middleware/host_authorization.rb +159 -0
  101. data/lib/action_dispatch/middleware/public_exceptions.rb +6 -3
  102. data/lib/action_dispatch/middleware/remote_ip.rb +30 -20
  103. data/lib/action_dispatch/middleware/request_id.rb +5 -6
  104. data/lib/action_dispatch/middleware/server_timing.rb +33 -0
  105. data/lib/action_dispatch/middleware/session/abstract_store.rb +16 -3
  106. data/lib/action_dispatch/middleware/session/cache_store.rb +11 -6
  107. data/lib/action_dispatch/middleware/session/cookie_store.rb +24 -19
  108. data/lib/action_dispatch/middleware/show_exceptions.rb +20 -11
  109. data/lib/action_dispatch/middleware/ssl.rb +20 -15
  110. data/lib/action_dispatch/middleware/stack.rb +79 -7
  111. data/lib/action_dispatch/middleware/static.rb +150 -94
  112. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  113. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  114. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  115. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +6 -11
  116. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  117. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +4 -2
  118. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +46 -36
  119. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +8 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +7 -0
  121. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +25 -6
  122. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
  123. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +9 -6
  124. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +4 -1
  125. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +121 -15
  126. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  128. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +5 -5
  129. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +4 -4
  130. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +5 -5
  131. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
  132. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +16 -2
  133. data/lib/action_dispatch/railtie.rb +16 -4
  134. data/lib/action_dispatch/request/session.rb +59 -22
  135. data/lib/action_dispatch/request/utils.rb +28 -2
  136. data/lib/action_dispatch/routing/inspector.rb +102 -54
  137. data/lib/action_dispatch/routing/mapper.rb +184 -156
  138. data/lib/action_dispatch/routing/polymorphic_routes.rb +21 -19
  139. data/lib/action_dispatch/routing/redirection.rb +4 -6
  140. data/lib/action_dispatch/routing/route_set.rb +83 -73
  141. data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
  142. data/lib/action_dispatch/routing/url_for.rb +2 -3
  143. data/lib/action_dispatch/routing.rb +23 -22
  144. data/lib/action_dispatch/system_test_case.rb +65 -16
  145. data/lib/action_dispatch/system_testing/browser.rb +43 -16
  146. data/lib/action_dispatch/system_testing/driver.rb +42 -10
  147. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +58 -12
  148. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +3 -10
  149. data/lib/action_dispatch/testing/assertion_response.rb +0 -1
  150. data/lib/action_dispatch/testing/assertions/response.rb +4 -7
  151. data/lib/action_dispatch/testing/assertions/routing.rb +20 -8
  152. data/lib/action_dispatch/testing/assertions.rb +3 -6
  153. data/lib/action_dispatch/testing/integration.rb +61 -30
  154. data/lib/action_dispatch/testing/request_encoder.rb +2 -2
  155. data/lib/action_dispatch/testing/test_process.rb +8 -6
  156. data/lib/action_dispatch/testing/test_request.rb +3 -3
  157. data/lib/action_dispatch/testing/test_response.rb +4 -32
  158. data/lib/action_dispatch.rb +15 -7
  159. data/lib/action_pack/gem_version.rb +4 -4
  160. data/lib/action_pack.rb +1 -1
  161. metadata +44 -25
  162. data/lib/action_controller/metal/force_ssl.rb +0 -99
  163. data/lib/action_dispatch/http/parameter_filter.rb +0 -86
  164. data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
  165. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -49
  166. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -120
  167. 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 = nil
13
+ @symbols = []
15
14
  end
16
15
 
17
- def each
18
- @mimes.each { |x| yield x }
16
+ def each(&block)
17
+ @mimes.each(&block)
19
18
  end
20
19
 
21
20
  def <<(type)
22
21
  @mimes << type
23
- @symbols = nil
22
+ @symbols << type.to_sym
24
23
  end
25
24
 
26
25
  def delete_if
27
- @mimes.delete_if { |x| yield x }.tap { @symbols = nil }
28
- end
29
-
30
- def symbols
31
- @symbols ||= map(&:to_sym)
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) { |k| yield k }
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 #:nodoc:
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 == "*/*".freeze # Default wildcard match to end of list.
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 #:nodoc:
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.ends_with? "+xml"
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 =~ type }
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 =~ /html/
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.to_s.ends_with? "?"
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
- (method.to_s.ends_with? "?") || super
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 "*/*", :all
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 `respond_to`
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.to_s.ends_with? "?"
350
+ method.end_with?("?")
334
351
  end
335
352
 
336
353
  def method_missing(method, *args)
337
- false if method.to_s.ends_with? "?"
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($!.message)
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) #:nodoc:
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
- # {'action' => 'my_action', 'controller' => 'my_controller'}
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
- my_logger = logger || ActiveSupport::Logger.new($stderr)
115
- my_logger.debug "Error occurred while parsing request parameters.\nContents:\n\n#{raw_post}"
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
- raise ParseError
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
- def #{env.sub(/^HTTP_/n, '').downcase} # def accept_charset
50
- get_header "#{env}".freeze # get_header "HTTP_ACCEPT_CHARSET".freeze
51
- end # end
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.binary_params_for?(action); false; end
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 = "#{controller_param.camelize}Controller"
88
- ActiveSupport::Dependencies.constantize(const_name)
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".freeze)
150
+ get_header("action_dispatch.routes")
140
151
  end
141
152
 
142
153
  def routes=(routes) # :nodoc:
143
- set_header("action_dispatch.routes".freeze, 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) #:nodoc:
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".freeze)
172
+ get_header("action_controller.instance")
162
173
  end
163
174
 
164
175
  def controller_instance=(controller) # :nodoc:
165
- set_header("action_controller.instance".freeze, controller)
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".freeze) == false)
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.to_s
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") =~ /XMLHttpRequest/i
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
- set_header "action_dispatch.remote_ip".freeze, remote_ip
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".freeze # :nodoc:
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.dup.force_encoding(Encoding::BINARY)
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 #:nodoc:
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
- if session && session.respond_to?(:destroy)
351
- session.destroy
352
- else
353
- self.session = {}
354
- end
360
+ session.destroy
355
361
  end
356
362
 
357
- def session=(session) #:nodoc:
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 =~ remote_addr && LOCALHOST =~ remote_ip
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".freeze, params)
418
+ set_header("action_dispatch.request.request_parameters", params)
411
419
  end
412
420
 
413
421
  def logger
414
- get_header("action_dispatch.logger".freeze)
422
+ get_header("action_dispatch.logger")
415
423
  end
416
424
 
417
425
  def commit_flash
418
426
  end
419
427
 
420
- def ssl?
421
- super || scheme == "wss".freeze
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