actionpack 6.0.6.1 → 6.1.7.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +416 -255
  3. data/MIT-LICENSE +1 -2
  4. data/lib/abstract_controller/base.rb +35 -2
  5. data/lib/abstract_controller/callbacks.rb +2 -2
  6. data/lib/abstract_controller/collector.rb +4 -2
  7. data/lib/abstract_controller/helpers.rb +105 -90
  8. data/lib/abstract_controller/railties/routes_helpers.rb +17 -1
  9. data/lib/abstract_controller/rendering.rb +9 -9
  10. data/lib/abstract_controller/translation.rb +8 -2
  11. data/lib/abstract_controller.rb +1 -0
  12. data/lib/action_controller/api.rb +2 -2
  13. data/lib/action_controller/base.rb +4 -2
  14. data/lib/action_controller/caching.rb +0 -1
  15. data/lib/action_controller/log_subscriber.rb +3 -3
  16. data/lib/action_controller/metal/conditional_get.rb +11 -3
  17. data/lib/action_controller/metal/content_security_policy.rb +1 -1
  18. data/lib/action_controller/metal/cookies.rb +3 -1
  19. data/lib/action_controller/metal/data_streaming.rb +1 -1
  20. data/lib/action_controller/metal/etag_with_template_digest.rb +3 -5
  21. data/lib/action_controller/metal/exceptions.rb +33 -0
  22. data/lib/action_controller/metal/head.rb +7 -4
  23. data/lib/action_controller/metal/helpers.rb +11 -1
  24. data/lib/action_controller/metal/http_authentication.rb +5 -2
  25. data/lib/action_controller/metal/implicit_render.rb +1 -1
  26. data/lib/action_controller/metal/instrumentation.rb +11 -9
  27. data/lib/action_controller/metal/live.rb +10 -1
  28. data/lib/action_controller/metal/logging.rb +20 -0
  29. data/lib/action_controller/metal/mime_responds.rb +6 -2
  30. data/lib/action_controller/metal/parameter_encoding.rb +35 -4
  31. data/lib/action_controller/metal/params_wrapper.rb +14 -8
  32. data/lib/action_controller/metal/permissions_policy.rb +46 -0
  33. data/lib/action_controller/metal/redirecting.rb +21 -2
  34. data/lib/action_controller/metal/rendering.rb +6 -0
  35. data/lib/action_controller/metal/request_forgery_protection.rb +1 -1
  36. data/lib/action_controller/metal/rescue.rb +1 -1
  37. data/lib/action_controller/metal/strong_parameters.rb +104 -16
  38. data/lib/action_controller/metal.rb +2 -2
  39. data/lib/action_controller/renderer.rb +23 -13
  40. data/lib/action_controller/test_case.rb +65 -56
  41. data/lib/action_controller.rb +2 -3
  42. data/lib/action_dispatch/http/cache.rb +18 -17
  43. data/lib/action_dispatch/http/content_security_policy.rb +6 -1
  44. data/lib/action_dispatch/http/filter_parameters.rb +1 -1
  45. data/lib/action_dispatch/http/filter_redirect.rb +1 -1
  46. data/lib/action_dispatch/http/headers.rb +3 -2
  47. data/lib/action_dispatch/http/mime_negotiation.rb +14 -8
  48. data/lib/action_dispatch/http/mime_type.rb +29 -16
  49. data/lib/action_dispatch/http/parameters.rb +1 -19
  50. data/lib/action_dispatch/http/permissions_policy.rb +173 -0
  51. data/lib/action_dispatch/http/request.rb +24 -8
  52. data/lib/action_dispatch/http/response.rb +17 -16
  53. data/lib/action_dispatch/http/url.rb +3 -2
  54. data/lib/action_dispatch/journey/formatter.rb +55 -30
  55. data/lib/action_dispatch/journey/gtg/builder.rb +22 -36
  56. data/lib/action_dispatch/journey/gtg/simulator.rb +8 -7
  57. data/lib/action_dispatch/journey/gtg/transition_table.rb +6 -4
  58. data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
  59. data/lib/action_dispatch/journey/nodes/node.rb +4 -3
  60. data/lib/action_dispatch/journey/parser.rb +13 -13
  61. data/lib/action_dispatch/journey/parser.y +1 -1
  62. data/lib/action_dispatch/journey/path/pattern.rb +13 -18
  63. data/lib/action_dispatch/journey/route.rb +7 -18
  64. data/lib/action_dispatch/journey/router/utils.rb +6 -4
  65. data/lib/action_dispatch/journey/router.rb +26 -30
  66. data/lib/action_dispatch/journey/visitors.rb +1 -1
  67. data/lib/action_dispatch/journey.rb +0 -2
  68. data/lib/action_dispatch/middleware/actionable_exceptions.rb +1 -1
  69. data/lib/action_dispatch/middleware/cookies.rb +89 -46
  70. data/lib/action_dispatch/middleware/debug_exceptions.rb +8 -15
  71. data/lib/action_dispatch/middleware/debug_view.rb +1 -1
  72. data/lib/action_dispatch/middleware/exception_wrapper.rb +28 -16
  73. data/lib/action_dispatch/middleware/host_authorization.rb +63 -14
  74. data/lib/action_dispatch/middleware/remote_ip.rb +5 -4
  75. data/lib/action_dispatch/middleware/request_id.rb +4 -5
  76. data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -2
  77. data/lib/action_dispatch/middleware/session/cookie_store.rb +2 -2
  78. data/lib/action_dispatch/middleware/show_exceptions.rb +12 -0
  79. data/lib/action_dispatch/middleware/ssl.rb +12 -7
  80. data/lib/action_dispatch/middleware/stack.rb +19 -1
  81. data/lib/action_dispatch/middleware/static.rb +154 -93
  82. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  83. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +2 -5
  84. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +2 -2
  85. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +2 -2
  86. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +100 -8
  87. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  88. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +21 -1
  89. data/lib/action_dispatch/railtie.rb +3 -2
  90. data/lib/action_dispatch/request/session.rb +2 -8
  91. data/lib/action_dispatch/request/utils.rb +26 -2
  92. data/lib/action_dispatch/routing/inspector.rb +8 -7
  93. data/lib/action_dispatch/routing/mapper.rb +102 -71
  94. data/lib/action_dispatch/routing/polymorphic_routes.rb +12 -11
  95. data/lib/action_dispatch/routing/redirection.rb +4 -4
  96. data/lib/action_dispatch/routing/route_set.rb +49 -41
  97. data/lib/action_dispatch/system_test_case.rb +35 -24
  98. data/lib/action_dispatch/system_testing/browser.rb +33 -27
  99. data/lib/action_dispatch/system_testing/driver.rb +6 -7
  100. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +47 -6
  101. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +4 -7
  102. data/lib/action_dispatch/testing/assertions/response.rb +2 -4
  103. data/lib/action_dispatch/testing/assertions/routing.rb +5 -5
  104. data/lib/action_dispatch/testing/assertions.rb +1 -1
  105. data/lib/action_dispatch/testing/integration.rb +40 -29
  106. data/lib/action_dispatch/testing/test_process.rb +32 -4
  107. data/lib/action_dispatch/testing/test_request.rb +3 -3
  108. data/lib/action_dispatch.rb +3 -2
  109. data/lib/action_pack/gem_version.rb +3 -3
  110. data/lib/action_pack.rb +1 -1
  111. metadata +18 -19
  112. data/lib/action_controller/metal/force_ssl.rb +0 -58
  113. data/lib/action_dispatch/http/parameter_filter.rb +0 -12
  114. data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
  115. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
  116. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -119
@@ -53,7 +53,7 @@ module ActionController #:nodoc:
53
53
  #
54
54
  # Show a 404 page in the browser:
55
55
  #
56
- # send_file '/path/to/404.html', type: 'text/html; charset=utf-8', status: 404
56
+ # send_file '/path/to/404.html', type: 'text/html; charset=utf-8', disposition: 'inline', status: 404
57
57
  #
58
58
  # Read about the other Content-* HTTP headers if you'd like to
59
59
  # provide the user with more information (such as Content-Description) in
@@ -26,10 +26,8 @@ module ActionController
26
26
  included do
27
27
  class_attribute :etag_with_template_digest, default: true
28
28
 
29
- ActiveSupport.on_load :action_view, yield: true do
30
- etag do |options|
31
- determine_template_etag(options) if etag_with_template_digest
32
- end
29
+ etag do |options|
30
+ determine_template_etag(options) if etag_with_template_digest
33
31
  end
34
32
  end
35
33
 
@@ -46,7 +44,7 @@ module ActionController
46
44
  # template digest from the ETag.
47
45
  def pick_template_for_etag(options)
48
46
  unless options[:template] == false
49
- options[:template] || "#{controller_path}/#{action_name}"
47
+ options[:template] || lookup_context.find_all(action_name, _prefixes).first&.virtual_path
50
48
  end
51
49
  end
52
50
 
@@ -23,6 +23,39 @@ module ActionController
23
23
  end
24
24
 
25
25
  class UrlGenerationError < ActionControllerError #:nodoc:
26
+ attr_reader :routes, :route_name, :method_name
27
+
28
+ def initialize(message, routes = nil, route_name = nil, method_name = nil)
29
+ @routes = routes
30
+ @route_name = route_name
31
+ @method_name = method_name
32
+
33
+ super(message)
34
+ end
35
+
36
+ class Correction
37
+ def initialize(error)
38
+ @error = error
39
+ end
40
+
41
+ def corrections
42
+ if @error.method_name
43
+ maybe_these = @error.routes.named_routes.helper_names.grep(/#{@error.route_name}/)
44
+ maybe_these -= [@error.method_name.to_s] # remove exact match
45
+
46
+ maybe_these.sort_by { |n|
47
+ DidYouMean::Jaro.distance(@error.route_name, n)
48
+ }.reverse.first(4)
49
+ else
50
+ []
51
+ end
52
+ end
53
+ end
54
+
55
+ # We may not have DYM, and DYM might not let us register error handlers
56
+ if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
57
+ DidYouMean.correct_error(self, Correction)
58
+ end
26
59
  end
27
60
 
28
61
  class MethodNotAllowed < ActionControllerError #:nodoc:
@@ -29,19 +29,22 @@ module ActionController
29
29
  content_type = options.delete(:content_type)
30
30
 
31
31
  options.each do |key, value|
32
- headers[key.to_s.dasherize.split("-").each { |v| v[0] = v[0].chr.upcase }.join("-")] = value.to_s
32
+ headers[key.to_s.split(/[-_]/).each { |v| v[0] = v[0].upcase }.join("-")] = value.to_s
33
33
  end
34
34
 
35
35
  self.status = status
36
36
  self.location = url_for(location) if location
37
37
 
38
- self.response_body = ""
39
-
40
38
  if include_content?(response_code)
41
- self.content_type = content_type || (Mime[formats.first] if formats) || Mime[:html]
39
+ unless self.media_type
40
+ self.content_type = content_type || (Mime[formats.first] if formats) || Mime[:html]
41
+ end
42
+
42
43
  response.charset = false
43
44
  end
44
45
 
46
+ self.response_body = ""
47
+
45
48
  true
46
49
  end
47
50
 
@@ -11,7 +11,12 @@ module ActionController
11
11
  #
12
12
  # In previous versions of \Rails the controller will include a helper which
13
13
  # matches the name of the controller, e.g., <tt>MyController</tt> will automatically
14
- # include <tt>MyHelper</tt>. To return old behavior set +config.action_controller.include_all_helpers+ to +false+.
14
+ # include <tt>MyHelper</tt>. You can revert to the old behavior with the following:
15
+ #
16
+ # # config/application.rb
17
+ # class Application < Rails::Application
18
+ # config.action_controller.include_all_helpers = false
19
+ # end
15
20
  #
16
21
  # Additional helpers can be specified using the +helper+ class method in ActionController::Base or any
17
22
  # controller which inherits from it.
@@ -73,6 +78,11 @@ module ActionController
73
78
  end
74
79
 
75
80
  # Provides a proxy to access helper methods from outside the view.
81
+ #
82
+ # Note that the proxy is rendered under a different view context.
83
+ # This may cause incorrect behaviour with capture methods. Consider
84
+ # using {helper}[rdoc-ref:AbstractController::Helpers::ClassMethods#helper]
85
+ # instead when using +capture+.
76
86
  def helpers
77
87
  @helper_proxy ||= begin
78
88
  proxy = ActionView::Base.empty
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "base64"
4
4
  require "active_support/security_utils"
5
+ require "active_support/core_ext/array/access"
5
6
 
6
7
  module ActionController
7
8
  # Makes it dead easy to do HTTP Basic, Digest and Token authentication.
@@ -76,6 +77,8 @@ module ActionController
76
77
 
77
78
  def http_basic_authenticate_or_request_with(name:, password:, realm: nil, message: nil)
78
79
  authenticate_or_request_with_http_basic(realm, message) do |given_name, given_password|
80
+ # This comparison uses & so that it doesn't short circuit and
81
+ # uses `secure_compare` so that length information isn't leaked.
79
82
  ActiveSupport::SecurityUtils.secure_compare(given_name, name) &
80
83
  ActiveSupport::SecurityUtils.secure_compare(given_password, password)
81
84
  end
@@ -136,7 +139,7 @@ module ActionController
136
139
  #
137
140
  # === Simple \Digest example
138
141
  #
139
- # require 'digest/md5'
142
+ # require "digest/md5"
140
143
  # class PostsController < ApplicationController
141
144
  # REALM = "SuperSecret"
142
145
  # USERS = {"dhh" => "secret", #plain text password
@@ -482,7 +485,7 @@ module ActionController
482
485
  def raw_params(auth)
483
486
  _raw_params = auth.sub(TOKEN_REGEX, "").split(/\s*#{AUTHN_PAIR_DELIMITERS}\s*/)
484
487
 
485
- if !(_raw_params.first =~ %r{\A#{TOKEN_KEY}})
488
+ if !_raw_params.first&.start_with?(TOKEN_KEY)
486
489
  _raw_params[0] = "#{TOKEN_KEY}#{_raw_params.first}"
487
490
  end
488
491
 
@@ -22,7 +22,7 @@ module ActionController
22
22
  # Third, if we DON'T find a template AND the request is a page load in a web
23
23
  # browser (technically, a non-XHR GET request for an HTML response) where
24
24
  # you reasonably expect to have rendered a template, then we raise
25
- # <tt>ActionView::UnknownFormat</tt> with an explanation.
25
+ # <tt>ActionController::MissingExactTemplate</tt> with an explanation.
26
26
  #
27
27
  # Finally, if we DON'T find a template AND the request isn't a browser page
28
28
  # load, then we implicitly respond with <tt>204 No Content</tt>.
@@ -16,10 +16,11 @@ module ActionController
16
16
 
17
17
  attr_internal :view_runtime
18
18
 
19
- def process_action(*args)
19
+ def process_action(*)
20
20
  raw_payload = {
21
21
  controller: self.class.name,
22
22
  action: action_name,
23
+ request: request,
23
24
  params: request.filtered_parameters,
24
25
  headers: request.headers,
25
26
  format: request.format.ref,
@@ -27,18 +28,19 @@ module ActionController
27
28
  path: request.fullpath
28
29
  }
29
30
 
30
- ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup)
31
+ ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload)
31
32
 
32
33
  ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
33
- super.tap do
34
- payload[:status] = response.status
35
- end
34
+ result = super
35
+ payload[:response] = response
36
+ payload[:status] = response.status
37
+ result
36
38
  ensure
37
39
  append_info_to_payload(payload)
38
40
  end
39
41
  end
40
42
 
41
- def render(*args)
43
+ def render(*)
42
44
  render_output = nil
43
45
  self.view_runtime = cleanup_view_runtime do
44
46
  Benchmark.ms { render_output = super }
@@ -59,8 +61,8 @@ module ActionController
59
61
  end
60
62
  end
61
63
 
62
- def redirect_to(*args)
63
- ActiveSupport::Notifications.instrument("redirect_to.action_controller") do |payload|
64
+ def redirect_to(*)
65
+ ActiveSupport::Notifications.instrument("redirect_to.action_controller", request: request) do |payload|
64
66
  result = super
65
67
  payload[:status] = response.status
66
68
  payload[:location] = response.filtered_location
@@ -70,7 +72,7 @@ module ActionController
70
72
 
71
73
  private
72
74
  # A hook invoked every time a before callback is halted.
73
- def halted_callback_hook(filter)
75
+ def halted_callback_hook(filter, _)
74
76
  ActiveSupport::Notifications.instrument("halted_callback.action_controller", filter: filter)
75
77
  end
76
78
 
@@ -127,6 +127,11 @@ module ActionController
127
127
  class Buffer < ActionDispatch::Response::Buffer #:nodoc:
128
128
  include MonitorMixin
129
129
 
130
+ class << self
131
+ attr_accessor :queue_size
132
+ end
133
+ @queue_size = 10
134
+
130
135
  # Ignore that the client has disconnected.
131
136
  #
132
137
  # If this value is `true`, calling `write` after the client
@@ -136,11 +141,11 @@ module ActionController
136
141
  attr_accessor :ignore_disconnect
137
142
 
138
143
  def initialize(response)
144
+ super(response, build_queue(self.class.queue_size))
139
145
  @error_callback = lambda { true }
140
146
  @cv = new_cond
141
147
  @aborted = false
142
148
  @ignore_disconnect = false
143
- super(response, SizedQueue.new(10))
144
149
  end
145
150
 
146
151
  def write(string)
@@ -214,6 +219,10 @@ module ActionController
214
219
  yield str
215
220
  end
216
221
  end
222
+
223
+ def build_queue(queue_size)
224
+ queue_size ? SizedQueue.new(queue_size) : Queue.new
225
+ end
217
226
  end
218
227
 
219
228
  class Response < ActionDispatch::Response #:nodoc: all
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionController
4
+ module Logging
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ # Set a different log level per request.
9
+ #
10
+ # # Use the debug log level if a particular cookie is set.
11
+ # class ApplicationController < ActionController::Base
12
+ # log_at :debug, if: -> { cookies[:debug] }
13
+ # end
14
+ #
15
+ def log_at(level, **options)
16
+ around_action ->(_, action) { logger.log_at(level, &action) }, **options
17
+ end
18
+ end
19
+ end
20
+ end
@@ -142,7 +142,7 @@ module ActionController #:nodoc:
142
142
  #
143
143
  # You can set the variant in a +before_action+:
144
144
  #
145
- # request.variant = :tablet if request.user_agent =~ /iPad/
145
+ # request.variant = :tablet if /iPad/.match?(request.user_agent)
146
146
  #
147
147
  # Respond to variants in the action just like you respond to formats:
148
148
  #
@@ -209,7 +209,7 @@ module ActionController #:nodoc:
209
209
  raise ActionController::RespondToMismatchError
210
210
  end
211
211
  _process_format(format)
212
- _set_rendered_content_type format
212
+ _set_rendered_content_type(format) unless collector.any_response?
213
213
  response = collector.response
214
214
  response.call if response
215
215
  else
@@ -268,6 +268,10 @@ module ActionController #:nodoc:
268
268
  end
269
269
  end
270
270
 
271
+ def any_response?
272
+ !@responses.fetch(format, false) && @responses[Mime::ALL]
273
+ end
274
+
271
275
  def response
272
276
  response = @responses.fetch(format, @responses[Mime::ALL])
273
277
  if response.is_a?(VariantCollector) # `format.html.phone` - variant inline syntax
@@ -12,11 +12,13 @@ module ActionController
12
12
  end
13
13
 
14
14
  def setup_param_encode # :nodoc:
15
- @_parameter_encodings = {}
15
+ @_parameter_encodings = Hash.new { |h, k| h[k] = {} }
16
16
  end
17
17
 
18
- def binary_params_for?(action) # :nodoc:
19
- @_parameter_encodings[action.to_s]
18
+ def action_encoding_template(action) # :nodoc:
19
+ if @_parameter_encodings.has_key?(action.to_s)
20
+ @_parameter_encodings[action.to_s]
21
+ end
20
22
  end
21
23
 
22
24
  # Specify that a given action's parameters should all be encoded as
@@ -44,7 +46,36 @@ module ActionController
44
46
  # encoded as ASCII-8BIT. This is useful in the case where an application
45
47
  # must handle data but encoding of the data is unknown, like file system data.
46
48
  def skip_parameter_encoding(action)
47
- @_parameter_encodings[action.to_s] = true
49
+ @_parameter_encodings[action.to_s] = Hash.new { Encoding::ASCII_8BIT }
50
+ end
51
+
52
+ # Specify the encoding for a parameter on an action.
53
+ # If not specified the default is UTF-8.
54
+ #
55
+ # You can specify a binary (ASCII_8BIT) parameter with:
56
+ #
57
+ # class RepositoryController < ActionController::Base
58
+ # # This specifies that file_path is not UTF-8 and is instead ASCII_8BIT
59
+ # param_encoding :show, :file_path, Encoding::ASCII_8BIT
60
+ #
61
+ # def show
62
+ # @repo = Repository.find_by_filesystem_path params[:file_path]
63
+ #
64
+ # # params[:repo_name] remains UTF-8 encoded
65
+ # @repo_name = params[:repo_name]
66
+ # end
67
+ #
68
+ # def index
69
+ # @repositories = Repository.all
70
+ # end
71
+ # end
72
+ #
73
+ # The file_path parameter on the show action would be encoded as ASCII-8BIT,
74
+ # but all other arguments will remain UTF-8 encoded.
75
+ # This is useful in the case where an application must handle data
76
+ # but encoding of the data is unknown, like file system data.
77
+ def param_encoding(action, param, encoding)
78
+ @_parameter_encodings[action.to_s][param.to_s] = encoding
48
79
  end
49
80
  end
50
81
  end
@@ -107,10 +107,14 @@ module ActionController
107
107
 
108
108
  unless super || exclude
109
109
  if m.respond_to?(:attribute_names) && m.attribute_names.any?
110
+ self.include = m.attribute_names
111
+
110
112
  if m.respond_to?(:stored_attributes) && !m.stored_attributes.empty?
111
- self.include = m.attribute_names + m.stored_attributes.values.flatten.map(&:to_s)
112
- else
113
- self.include = m.attribute_names
113
+ self.include += m.stored_attributes.values.flatten.map(&:to_s)
114
+ end
115
+
116
+ if m.respond_to?(:attribute_aliases) && m.attribute_aliases.any?
117
+ self.include += m.attribute_aliases.keys
114
118
  end
115
119
 
116
120
  if m.respond_to?(:nested_attributes_options) && m.nested_attributes_options.keys.any?
@@ -151,7 +155,7 @@ module ActionController
151
155
  # try to find Foo::Bar::User, Foo::User and finally User.
152
156
  def _default_wrap_model
153
157
  return nil if klass.anonymous?
154
- model_name = klass.name.sub(/Controller$/, "").classify
158
+ model_name = klass.name.delete_suffix("Controller").classify
155
159
 
156
160
  begin
157
161
  if model_klass = model_name.safe_constantize
@@ -189,7 +193,7 @@ module ActionController
189
193
  #
190
194
  # wrap_parameters Person
191
195
  # # wraps parameters by determining the wrapper key from Person class
192
- # (+person+, in this case) and the list of attribute names
196
+ # # (+person+, in this case) and the list of attribute names
193
197
  #
194
198
  # wrap_parameters include: [:username, :title]
195
199
  # # wraps only +:username+ and +:title+ attributes from parameters.
@@ -240,7 +244,7 @@ module ActionController
240
244
 
241
245
  # Performs parameters wrapping upon the request. Called automatically
242
246
  # by the metal call stack.
243
- def process_action(*args)
247
+ def process_action(*)
244
248
  _perform_parameter_wrapping if _wrapper_enabled?
245
249
  super
246
250
  end
@@ -264,9 +268,11 @@ module ActionController
264
268
  def _extract_parameters(parameters)
265
269
  if include_only = _wrapper_options.include
266
270
  parameters.slice(*include_only)
271
+ elsif _wrapper_options.exclude
272
+ exclude = _wrapper_options.exclude + EXCLUDE_PARAMETERS
273
+ parameters.except(*exclude)
267
274
  else
268
- exclude = _wrapper_options.exclude || []
269
- parameters.except(*(exclude + EXCLUDE_PARAMETERS))
275
+ parameters.except(*EXCLUDE_PARAMETERS)
270
276
  end
271
277
  end
272
278
 
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionController #:nodoc:
4
+ # HTTP Permissions Policy is a web standard for defining a mechanism to
5
+ # allow and deny the use of browser permissions in its own context, and
6
+ # in content within any <iframe> elements in the document.
7
+ #
8
+ # Full details of HTTP Permissions Policy specification and guidelines can
9
+ # be found at MDN:
10
+ #
11
+ # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy
12
+ #
13
+ # Examples of usage:
14
+ #
15
+ # # Global policy
16
+ # Rails.application.config.permissions_policy do |f|
17
+ # f.camera :none
18
+ # f.gyroscope :none
19
+ # f.microphone :none
20
+ # f.usb :none
21
+ # f.fullscreen :self
22
+ # f.payment :self, "https://secure.example.com"
23
+ # end
24
+ #
25
+ # # Controller level policy
26
+ # class PagesController < ApplicationController
27
+ # permissions_policy do |p|
28
+ # p.geolocation "https://example.com"
29
+ # end
30
+ # end
31
+ module PermissionsPolicy
32
+ extend ActiveSupport::Concern
33
+
34
+ module ClassMethods
35
+ def permissions_policy(**options, &block)
36
+ before_action(options) do
37
+ if block_given?
38
+ policy = request.permissions_policy.clone
39
+ yield policy
40
+ request.permissions_policy = policy
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -7,6 +7,10 @@ module ActionController
7
7
  include AbstractController::Logger
8
8
  include ActionController::UrlFor
9
9
 
10
+ ILLEGAL_HEADER_VALUE_REGEX = /[\x00-\x08\x0A-\x1F]/.freeze
11
+
12
+ class UnsafeRedirectError < StandardError; end
13
+
10
14
  # Redirects the browser to the target specified in +options+. This parameter can be any one of:
11
15
  #
12
16
  # * <tt>Hash</tt> - The URL will be generated by calling url_for with the +options+.
@@ -60,7 +64,11 @@ module ActionController
60
64
  raise AbstractController::DoubleRenderError if response_body
61
65
 
62
66
  self.status = _extract_redirect_to_status(options, response_options)
63
- self.location = _compute_redirect_to_location(request, options)
67
+
68
+ redirect_to_location = _compute_redirect_to_location(request, options)
69
+ _ensure_url_is_http_header_safe(redirect_to_location)
70
+
71
+ self.location = redirect_to_location
64
72
  self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(response.location)}\">redirected</a>.</body></html>"
65
73
  end
66
74
 
@@ -85,7 +93,7 @@ module ActionController
85
93
  # * <tt>:fallback_location</tt> - The default fallback location that will be used on missing +Referer+ header.
86
94
  # * <tt>:allow_other_host</tt> - Allow or disallow redirection to the host that is different to the current host, defaults to true.
87
95
  #
88
- # All other options that can be passed to <tt>redirect_to</tt> are accepted as
96
+ # All other options that can be passed to #redirect_to are accepted as
89
97
  # options and the behavior is identical.
90
98
  def redirect_back(fallback_location:, allow_other_host: true, **args)
91
99
  referer = request.headers["Referer"]
@@ -129,5 +137,16 @@ module ActionController
129
137
  rescue ArgumentError, URI::Error
130
138
  false
131
139
  end
140
+
141
+ def _ensure_url_is_http_header_safe(url)
142
+ # Attempt to comply with the set of valid token characters
143
+ # defined for an HTTP header value in
144
+ # https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6
145
+ if url.match(ILLEGAL_HEADER_VALUE_REGEX)
146
+ msg = "The redirect URL #{url} contains one or more illegal HTTP header field character. " \
147
+ "Set of legal characters defined in https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6"
148
+ raise UnsafeRedirectError, msg
149
+ end
150
+ end
132
151
  end
133
152
  end
@@ -77,6 +77,12 @@ module ActionController
77
77
  end
78
78
  end
79
79
 
80
+ def _set_vary_header
81
+ if response.headers["Vary"].blank? && request.should_apply_vary_header?
82
+ response.headers["Vary"] = "Accept"
83
+ end
84
+ end
85
+
80
86
  # Normalize arguments by catching blocks and setting them on :update.
81
87
  def _normalize_args(action = nil, options = {}, &blk)
82
88
  options = super
@@ -386,7 +386,7 @@ module ActionController #:nodoc:
386
386
  if per_form_csrf_tokens
387
387
  correct_token = per_form_csrf_token(
388
388
  session,
389
- normalize_action_path(request.fullpath),
389
+ request.path.chomp("/"),
390
390
  request.request_method
391
391
  )
392
392
 
@@ -18,7 +18,7 @@ module ActionController #:nodoc:
18
18
  end
19
19
 
20
20
  private
21
- def process_action(*args)
21
+ def process_action(*)
22
22
  super
23
23
  rescue Exception => exception
24
24
  request.env["action_dispatch.show_detailed_exceptions"] ||= show_detailed_exceptions?