actionpack 5.2.7.1 → 6.1.7.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (155) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +427 -338
  3. data/MIT-LICENSE +1 -2
  4. data/README.rdoc +4 -3
  5. data/lib/abstract_controller/base.rb +38 -4
  6. data/lib/abstract_controller/caching/fragments.rb +6 -22
  7. data/lib/abstract_controller/caching.rb +1 -1
  8. data/lib/abstract_controller/callbacks.rb +14 -2
  9. data/lib/abstract_controller/collector.rb +5 -4
  10. data/lib/abstract_controller/helpers.rb +106 -90
  11. data/lib/abstract_controller/railties/routes_helpers.rb +17 -1
  12. data/lib/abstract_controller/rendering.rb +9 -9
  13. data/lib/abstract_controller/translation.rb +11 -5
  14. data/lib/abstract_controller.rb +1 -0
  15. data/lib/action_controller/api.rb +4 -3
  16. data/lib/action_controller/base.rb +6 -9
  17. data/lib/action_controller/caching.rb +1 -3
  18. data/lib/action_controller/log_subscriber.rb +10 -7
  19. data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
  20. data/lib/action_controller/metal/conditional_get.rb +19 -5
  21. data/lib/action_controller/metal/content_security_policy.rb +1 -2
  22. data/lib/action_controller/metal/cookies.rb +3 -1
  23. data/lib/action_controller/metal/data_streaming.rb +6 -7
  24. data/lib/action_controller/metal/default_headers.rb +17 -0
  25. data/lib/action_controller/metal/etag_with_template_digest.rb +4 -6
  26. data/lib/action_controller/metal/exceptions.rb +56 -2
  27. data/lib/action_controller/metal/flash.rb +5 -5
  28. data/lib/action_controller/metal/head.rb +7 -4
  29. data/lib/action_controller/metal/helpers.rb +14 -5
  30. data/lib/action_controller/metal/http_authentication.rb +25 -23
  31. data/lib/action_controller/metal/implicit_render.rb +5 -15
  32. data/lib/action_controller/metal/instrumentation.rb +13 -14
  33. data/lib/action_controller/metal/live.rb +39 -32
  34. data/lib/action_controller/metal/logging.rb +20 -0
  35. data/lib/action_controller/metal/mime_responds.rb +19 -4
  36. data/lib/action_controller/metal/parameter_encoding.rb +35 -4
  37. data/lib/action_controller/metal/params_wrapper.rb +32 -22
  38. data/lib/action_controller/metal/permissions_policy.rb +46 -0
  39. data/lib/action_controller/metal/redirecting.rb +26 -7
  40. data/lib/action_controller/metal/renderers.rb +4 -4
  41. data/lib/action_controller/metal/rendering.rb +8 -3
  42. data/lib/action_controller/metal/request_forgery_protection.rb +26 -49
  43. data/lib/action_controller/metal/rescue.rb +1 -1
  44. data/lib/action_controller/metal/streaming.rb +0 -1
  45. data/lib/action_controller/metal/strong_parameters.rb +168 -59
  46. data/lib/action_controller/metal/url_for.rb +1 -1
  47. data/lib/action_controller/metal.rb +10 -8
  48. data/lib/action_controller/railties/helpers.rb +1 -1
  49. data/lib/action_controller/renderer.rb +37 -13
  50. data/lib/action_controller/template_assertions.rb +1 -1
  51. data/lib/action_controller/test_case.rb +71 -63
  52. data/lib/action_controller.rb +7 -4
  53. data/lib/action_dispatch/http/cache.rb +32 -28
  54. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  55. data/lib/action_dispatch/http/content_security_policy.rb +34 -18
  56. data/lib/action_dispatch/http/filter_parameters.rb +9 -8
  57. data/lib/action_dispatch/http/filter_redirect.rb +2 -3
  58. data/lib/action_dispatch/http/headers.rb +4 -4
  59. data/lib/action_dispatch/http/mime_negotiation.rb +26 -13
  60. data/lib/action_dispatch/http/mime_type.rb +43 -24
  61. data/lib/action_dispatch/http/parameters.rb +14 -23
  62. data/lib/action_dispatch/http/permissions_policy.rb +173 -0
  63. data/lib/action_dispatch/http/request.rb +45 -22
  64. data/lib/action_dispatch/http/response.rb +45 -25
  65. data/lib/action_dispatch/http/upload.rb +9 -1
  66. data/lib/action_dispatch/http/url.rb +82 -82
  67. data/lib/action_dispatch/journey/formatter.rb +55 -31
  68. data/lib/action_dispatch/journey/gtg/builder.rb +22 -37
  69. data/lib/action_dispatch/journey/gtg/simulator.rb +8 -7
  70. data/lib/action_dispatch/journey/gtg/transition_table.rb +6 -5
  71. data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
  72. data/lib/action_dispatch/journey/nodes/node.rb +13 -11
  73. data/lib/action_dispatch/journey/parser.rb +13 -13
  74. data/lib/action_dispatch/journey/parser.y +1 -1
  75. data/lib/action_dispatch/journey/path/pattern.rb +19 -21
  76. data/lib/action_dispatch/journey/route.rb +10 -20
  77. data/lib/action_dispatch/journey/router/utils.rb +14 -12
  78. data/lib/action_dispatch/journey/router.rb +26 -34
  79. data/lib/action_dispatch/journey/routes.rb +0 -2
  80. data/lib/action_dispatch/journey/scanner.rb +10 -4
  81. data/lib/action_dispatch/journey/visitors.rb +1 -4
  82. data/lib/action_dispatch/journey.rb +0 -2
  83. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  84. data/lib/action_dispatch/middleware/callbacks.rb +2 -4
  85. data/lib/action_dispatch/middleware/cookies.rb +150 -123
  86. data/lib/action_dispatch/middleware/debug_exceptions.rb +43 -66
  87. data/lib/action_dispatch/middleware/debug_locks.rb +5 -5
  88. data/lib/action_dispatch/middleware/debug_view.rb +66 -0
  89. data/lib/action_dispatch/middleware/exception_wrapper.rb +75 -30
  90. data/lib/action_dispatch/middleware/flash.rb +1 -1
  91. data/lib/action_dispatch/middleware/host_authorization.rb +170 -0
  92. data/lib/action_dispatch/middleware/public_exceptions.rb +6 -3
  93. data/lib/action_dispatch/middleware/remote_ip.rb +14 -16
  94. data/lib/action_dispatch/middleware/request_id.rb +5 -6
  95. data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -3
  96. data/lib/action_dispatch/middleware/session/cookie_store.rb +3 -9
  97. data/lib/action_dispatch/middleware/show_exceptions.rb +13 -2
  98. data/lib/action_dispatch/middleware/ssl.rb +20 -15
  99. data/lib/action_dispatch/middleware/stack.rb +56 -2
  100. data/lib/action_dispatch/middleware/static.rb +153 -93
  101. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  102. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  103. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  104. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +3 -1
  105. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  106. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +4 -2
  107. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +45 -35
  108. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -0
  109. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -0
  110. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +23 -4
  111. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
  112. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +6 -3
  113. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +4 -1
  114. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +104 -8
  115. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  116. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  117. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +2 -2
  118. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -1
  119. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +2 -2
  120. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  121. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +24 -1
  122. data/lib/action_dispatch/railtie.rb +8 -2
  123. data/lib/action_dispatch/request/session.rb +11 -10
  124. data/lib/action_dispatch/request/utils.rb +26 -2
  125. data/lib/action_dispatch/routing/inspector.rb +100 -52
  126. data/lib/action_dispatch/routing/mapper.rb +155 -103
  127. data/lib/action_dispatch/routing/polymorphic_routes.rb +13 -15
  128. data/lib/action_dispatch/routing/redirection.rb +4 -4
  129. data/lib/action_dispatch/routing/route_set.rb +71 -69
  130. data/lib/action_dispatch/routing/url_for.rb +2 -2
  131. data/lib/action_dispatch/routing.rb +21 -20
  132. data/lib/action_dispatch/system_test_case.rb +60 -11
  133. data/lib/action_dispatch/system_testing/browser.rb +53 -16
  134. data/lib/action_dispatch/system_testing/driver.rb +11 -3
  135. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +49 -7
  136. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +8 -10
  137. data/lib/action_dispatch/testing/assertion_response.rb +0 -1
  138. data/lib/action_dispatch/testing/assertions/response.rb +4 -7
  139. data/lib/action_dispatch/testing/assertions/routing.rb +20 -8
  140. data/lib/action_dispatch/testing/assertions.rb +1 -1
  141. data/lib/action_dispatch/testing/integration.rb +60 -28
  142. data/lib/action_dispatch/testing/request_encoder.rb +2 -2
  143. data/lib/action_dispatch/testing/test_process.rb +32 -4
  144. data/lib/action_dispatch/testing/test_request.rb +3 -3
  145. data/lib/action_dispatch/testing/test_response.rb +4 -32
  146. data/lib/action_dispatch.rb +9 -3
  147. data/lib/action_pack/gem_version.rb +3 -3
  148. data/lib/action_pack.rb +1 -1
  149. metadata +36 -23
  150. data/lib/action_controller/metal/force_ssl.rb +0 -99
  151. data/lib/action_dispatch/http/parameter_filter.rb +0 -86
  152. data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
  153. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -49
  154. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -120
  155. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +0 -26
@@ -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,20 +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
- begin
34
- result = super
35
- payload[:status] = response.status
36
- result
37
- ensure
38
- append_info_to_payload(payload)
39
- end
34
+ result = super
35
+ payload[:response] = response
36
+ payload[:status] = response.status
37
+ result
38
+ ensure
39
+ append_info_to_payload(payload)
40
40
  end
41
41
  end
42
42
 
43
- def render(*args)
43
+ def render(*)
44
44
  render_output = nil
45
45
  self.view_runtime = cleanup_view_runtime do
46
46
  Benchmark.ms { render_output = super }
@@ -61,8 +61,8 @@ module ActionController
61
61
  end
62
62
  end
63
63
 
64
- def redirect_to(*args)
65
- 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|
66
66
  result = super
67
67
  payload[:status] = response.status
68
68
  payload[:location] = response.filtered_location
@@ -71,9 +71,8 @@ module ActionController
71
71
  end
72
72
 
73
73
  private
74
-
75
74
  # A hook invoked every time a before callback is halted.
76
- def halted_callback_hook(filter)
75
+ def halted_callback_hook(filter, _)
77
76
  ActiveSupport::Notifications.instrument("halted_callback.action_controller", filter: filter)
78
77
  end
79
78
 
@@ -86,7 +86,7 @@ module ActionController
86
86
  # Note: SSEs are not currently supported by IE. However, they are supported
87
87
  # by Chrome, Firefox, Opera, and Safari.
88
88
  class SSE
89
- WHITELISTED_OPTIONS = %w( retry event id )
89
+ PERMITTED_OPTIONS = %w( retry event id )
90
90
 
91
91
  def initialize(stream, options = {})
92
92
  @stream = stream
@@ -107,17 +107,16 @@ module ActionController
107
107
  end
108
108
 
109
109
  private
110
-
111
110
  def perform_write(json, options)
112
111
  current_options = @options.merge(options).stringify_keys
113
112
 
114
- WHITELISTED_OPTIONS.each do |option_name|
113
+ PERMITTED_OPTIONS.each do |option_name|
115
114
  if (option_value = current_options[option_name])
116
115
  @stream.write "#{option_name}: #{option_value}\n"
117
116
  end
118
117
  end
119
118
 
120
- message = json.gsub("\n".freeze, "\ndata: ".freeze)
119
+ message = json.gsub("\n", "\ndata: ")
121
120
  @stream.write "data: #{message}\n\n"
122
121
  end
123
122
  end
@@ -128,6 +127,11 @@ module ActionController
128
127
  class Buffer < ActionDispatch::Response::Buffer #:nodoc:
129
128
  include MonitorMixin
130
129
 
130
+ class << self
131
+ attr_accessor :queue_size
132
+ end
133
+ @queue_size = 10
134
+
131
135
  # Ignore that the client has disconnected.
132
136
  #
133
137
  # If this value is `true`, calling `write` after the client
@@ -137,16 +141,16 @@ module ActionController
137
141
  attr_accessor :ignore_disconnect
138
142
 
139
143
  def initialize(response)
144
+ super(response, build_queue(self.class.queue_size))
140
145
  @error_callback = lambda { true }
141
146
  @cv = new_cond
142
147
  @aborted = false
143
148
  @ignore_disconnect = false
144
- super(response, SizedQueue.new(10))
145
149
  end
146
150
 
147
151
  def write(string)
148
152
  unless @response.committed?
149
- @response.set_header "Cache-Control", "no-cache"
153
+ @response.headers["Cache-Control"] ||= "no-cache"
150
154
  @response.delete_header "Content-Length"
151
155
  end
152
156
 
@@ -205,7 +209,6 @@ module ActionController
205
209
  end
206
210
 
207
211
  private
208
-
209
212
  def each_chunk(&block)
210
213
  loop do
211
214
  str = nil
@@ -216,11 +219,14 @@ module ActionController
216
219
  yield str
217
220
  end
218
221
  end
222
+
223
+ def build_queue(queue_size)
224
+ queue_size ? SizedQueue.new(queue_size) : Queue.new
225
+ end
219
226
  end
220
227
 
221
228
  class Response < ActionDispatch::Response #:nodoc: all
222
229
  private
223
-
224
230
  def before_committed
225
231
  super
226
232
  jar = request.cookie_jar
@@ -280,33 +286,34 @@ module ActionController
280
286
  raise error if error
281
287
  end
282
288
 
283
- # Spawn a new thread to serve up the controller in. This is to get
284
- # around the fact that Rack isn't based around IOs and we need to use
285
- # a thread to stream data from the response bodies. Nobody should call
286
- # this method except in Rails internals. Seriously!
287
- def new_controller_thread # :nodoc:
288
- Thread.new {
289
- t2 = Thread.current
290
- t2.abort_on_exception = true
291
- yield
292
- }
293
- end
294
-
295
- def log_error(exception)
296
- logger = ActionController::Base.logger
297
- return unless logger
298
-
299
- logger.fatal do
300
- message = "\n#{exception.class} (#{exception.message}):\n".dup
301
- message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
302
- message << " " << exception.backtrace.join("\n ")
303
- "#{message}\n\n"
304
- end
305
- end
306
-
307
289
  def response_body=(body)
308
290
  super
309
291
  response.close if response
310
292
  end
293
+
294
+ private
295
+ # Spawn a new thread to serve up the controller in. This is to get
296
+ # around the fact that Rack isn't based around IOs and we need to use
297
+ # a thread to stream data from the response bodies. Nobody should call
298
+ # this method except in Rails internals. Seriously!
299
+ def new_controller_thread # :nodoc:
300
+ Thread.new {
301
+ t2 = Thread.current
302
+ t2.abort_on_exception = true
303
+ yield
304
+ }
305
+ end
306
+
307
+ def log_error(exception)
308
+ logger = ActionController::Base.logger
309
+ return unless logger
310
+
311
+ logger.fatal do
312
+ message = +"\n#{exception.class} (#{exception.message}):\n"
313
+ message << exception.annotated_source_code.to_s if exception.respond_to?(:annotated_source_code)
314
+ message << " " << exception.backtrace.join("\n ")
315
+ "#{message}\n\n"
316
+ end
317
+ end
311
318
  end
312
319
  end
@@ -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
@@ -11,7 +11,7 @@ module ActionController #:nodoc:
11
11
  # @people = Person.all
12
12
  # end
13
13
  #
14
- # That action implicitly responds to all formats, but formats can also be whitelisted:
14
+ # That action implicitly responds to all formats, but formats can also be explicitly enumerated:
15
15
  #
16
16
  # def index
17
17
  # @people = Person.all
@@ -105,7 +105,7 @@ module ActionController #:nodoc:
105
105
  #
106
106
  # Mime::Type.register "image/jpg", :jpg
107
107
  #
108
- # Respond to also allows you to specify a common block for different formats by using +any+:
108
+ # +respond_to+ also allows you to specify a common block for different formats by using +any+:
109
109
  #
110
110
  # def index
111
111
  # @people = Person.all
@@ -124,6 +124,14 @@ module ActionController #:nodoc:
124
124
  #
125
125
  # render json: @people
126
126
  #
127
+ # +any+ can also be used with no arguments, in which case it will be used for any format requested by
128
+ # the user:
129
+ #
130
+ # respond_to do |format|
131
+ # format.html
132
+ # format.any { redirect_to support_path }
133
+ # end
134
+ #
127
135
  # Formats can have different variants.
128
136
  #
129
137
  # The request variant is a specialization of the request format, like <tt>:tablet</tt>,
@@ -134,7 +142,7 @@ module ActionController #:nodoc:
134
142
  #
135
143
  # You can set the variant in a +before_action+:
136
144
  #
137
- # request.variant = :tablet if request.user_agent =~ /iPad/
145
+ # request.variant = :tablet if /iPad/.match?(request.user_agent)
138
146
  #
139
147
  # Respond to variants in the action just like you respond to formats:
140
148
  #
@@ -197,8 +205,11 @@ module ActionController #:nodoc:
197
205
  yield collector if block_given?
198
206
 
199
207
  if format = collector.negotiate_format(request)
208
+ if media_type && media_type != format
209
+ raise ActionController::RespondToMismatchError
210
+ end
200
211
  _process_format(format)
201
- _set_rendered_content_type format
212
+ _set_rendered_content_type(format) unless collector.any_response?
202
213
  response = collector.response
203
214
  response.call if response
204
215
  else
@@ -257,6 +268,10 @@ module ActionController #:nodoc:
257
268
  end
258
269
  end
259
270
 
271
+ def any_response?
272
+ !@responses.fetch(format, false) && @responses[Mime::ALL]
273
+ end
274
+
260
275
  def response
261
276
  response = @responses.fetch(format, @responses[Mime::ALL])
262
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,15 +107,19 @@ 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?
117
121
  self.include += m.nested_attributes_options.keys.map do |key|
118
- key.to_s.dup.concat("_attributes")
122
+ (+key.to_s).concat("_attributes")
119
123
  end
120
124
  end
121
125
 
@@ -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,24 +244,12 @@ 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)
244
- if _wrapper_enabled?
245
- wrapped_hash = _wrap_parameters request.request_parameters
246
- wrapped_keys = request.request_parameters.keys
247
- wrapped_filtered_hash = _wrap_parameters request.filtered_parameters.slice(*wrapped_keys)
248
-
249
- # This will make the wrapped hash accessible from controller and view.
250
- request.parameters.merge! wrapped_hash
251
- request.request_parameters.merge! wrapped_hash
252
-
253
- # This will display the wrapped hash in the log file.
254
- request.filtered_parameters.merge! wrapped_filtered_hash
255
- end
247
+ def process_action(*)
248
+ _perform_parameter_wrapping if _wrapper_enabled?
256
249
  super
257
250
  end
258
251
 
259
252
  private
260
-
261
253
  # Returns the wrapper key which will be used to store wrapped parameters.
262
254
  def _wrapper_key
263
255
  _wrapper_options.name
@@ -276,9 +268,11 @@ module ActionController
276
268
  def _extract_parameters(parameters)
277
269
  if include_only = _wrapper_options.include
278
270
  parameters.slice(*include_only)
271
+ elsif _wrapper_options.exclude
272
+ exclude = _wrapper_options.exclude + EXCLUDE_PARAMETERS
273
+ parameters.except(*exclude)
279
274
  else
280
- exclude = _wrapper_options.exclude || []
281
- parameters.except(*(exclude + EXCLUDE_PARAMETERS))
275
+ parameters.except(*EXCLUDE_PARAMETERS)
282
276
  end
283
277
  end
284
278
 
@@ -287,7 +281,23 @@ module ActionController
287
281
  return false unless request.has_content_type?
288
282
 
289
283
  ref = request.content_mime_type.ref
284
+
290
285
  _wrapper_formats.include?(ref) && _wrapper_key && !request.parameters.key?(_wrapper_key)
286
+ rescue ActionDispatch::Http::Parameters::ParseError
287
+ false
288
+ end
289
+
290
+ def _perform_parameter_wrapping
291
+ wrapped_hash = _wrap_parameters request.request_parameters
292
+ wrapped_keys = request.request_parameters.keys
293
+ wrapped_filtered_hash = _wrap_parameters request.filtered_parameters.slice(*wrapped_keys)
294
+
295
+ # This will make the wrapped hash accessible from controller and view.
296
+ request.parameters.merge! wrapped_hash
297
+ request.request_parameters.merge! wrapped_hash
298
+
299
+ # This will display the wrapped hash in the log file.
300
+ request.filtered_parameters.merge! wrapped_filtered_hash
291
301
  end
292
302
  end
293
303
  end
@@ -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+.
@@ -55,12 +59,16 @@ module ActionController
55
59
  # Statements after +redirect_to+ in our controller get executed, so +redirect_to+ doesn't stop the execution of the function.
56
60
  # To terminate the execution of the function immediately after the +redirect_to+, use return.
57
61
  # redirect_to post_url(@post) and return
58
- def redirect_to(options = {}, response_status = {})
62
+ def redirect_to(options = {}, response_options = {})
59
63
  raise ActionControllerError.new("Cannot redirect to nil!") unless options
60
64
  raise AbstractController::DoubleRenderError if response_body
61
65
 
62
- self.status = _extract_redirect_to_status(options, response_status)
63
- self.location = _compute_redirect_to_location(request, options)
66
+ self.status = _extract_redirect_to_status(options, response_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"]
@@ -114,11 +122,11 @@ module ActionController
114
122
  public :_compute_redirect_to_location
115
123
 
116
124
  private
117
- def _extract_redirect_to_status(options, response_status)
125
+ def _extract_redirect_to_status(options, response_options)
118
126
  if options.is_a?(Hash) && options.key?(:status)
119
127
  Rack::Utils.status_code(options.delete(:status))
120
- elsif response_status.key?(:status)
121
- Rack::Utils.status_code(response_status[:status])
128
+ elsif response_options.key?(:status)
129
+ Rack::Utils.status_code(response_options[:status])
122
130
  else
123
131
  302
124
132
  end
@@ -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
@@ -157,24 +157,24 @@ module ActionController
157
157
  json = json.to_json(options) unless json.kind_of?(String)
158
158
 
159
159
  if options[:callback].present?
160
- if content_type.nil? || content_type == Mime[:json]
160
+ if media_type.nil? || media_type == Mime[:json]
161
161
  self.content_type = Mime[:js]
162
162
  end
163
163
 
164
164
  "/**/#{options[:callback]}(#{json})"
165
165
  else
166
- self.content_type ||= Mime[:json]
166
+ self.content_type = Mime[:json] if media_type.nil?
167
167
  json
168
168
  end
169
169
  end
170
170
 
171
171
  add :js do |js, options|
172
- self.content_type ||= Mime[:js]
172
+ self.content_type = Mime[:js] if media_type.nil?
173
173
  js.respond_to?(:to_js) ? js.to_js(options) : js
174
174
  end
175
175
 
176
176
  add :xml do |xml, options|
177
- self.content_type ||= Mime[:xml]
177
+ self.content_type = Mime[:xml] if media_type.nil?
178
178
  xml.respond_to?(:to_xml) ? xml.to_xml(options) : xml
179
179
  end
180
180
  end
@@ -40,7 +40,7 @@ module ActionController
40
40
  def render_to_string(*)
41
41
  result = super
42
42
  if result.respond_to?(:each)
43
- string = "".dup
43
+ string = +""
44
44
  result.each { |r| string << r }
45
45
  string
46
46
  else
@@ -53,7 +53,6 @@ module ActionController
53
53
  end
54
54
 
55
55
  private
56
-
57
56
  def _process_variant(options)
58
57
  if defined?(request) && !request.nil? && request.variant.present?
59
58
  options[:variant] = request.variant
@@ -73,11 +72,17 @@ module ActionController
73
72
  end
74
73
 
75
74
  def _set_rendered_content_type(format)
76
- if format && !response.content_type
75
+ if format && !response.media_type
77
76
  self.content_type = format.to_s
78
77
  end
79
78
  end
80
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
+
81
86
  # Normalize arguments by catching blocks and setting them on :update.
82
87
  def _normalize_args(action = nil, options = {}, &blk)
83
88
  options = super