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
@@ -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
@@ -125,9 +124,14 @@ module ActionController
125
124
  class ClientDisconnected < RuntimeError
126
125
  end
127
126
 
128
- class Buffer < ActionDispatch::Response::Buffer #:nodoc:
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
 
@@ -164,6 +168,11 @@ module ActionController
164
168
  end
165
169
  end
166
170
 
171
+ # Same as +write+ but automatically include a newline at the end of the string.
172
+ def writeln(string)
173
+ write string.end_with?("\n") ? string : "#{string}\n"
174
+ end
175
+
167
176
  # Write a 'close' event to the buffer; the producer/writing thread
168
177
  # uses this to notify us that it's finished supplying content.
169
178
  #
@@ -205,7 +214,6 @@ module ActionController
205
214
  end
206
215
 
207
216
  private
208
-
209
217
  def each_chunk(&block)
210
218
  loop do
211
219
  str = nil
@@ -216,11 +224,14 @@ module ActionController
216
224
  yield str
217
225
  end
218
226
  end
227
+
228
+ def build_queue(queue_size)
229
+ queue_size ? SizedQueue.new(queue_size) : Queue.new
230
+ end
219
231
  end
220
232
 
221
- class Response < ActionDispatch::Response #:nodoc: all
233
+ class Response < ActionDispatch::Response # :nodoc: all
222
234
  private
223
-
224
235
  def before_committed
225
236
  super
226
237
  jar = request.cookie_jar
@@ -280,33 +291,69 @@ module ActionController
280
291
  raise error if error
281
292
  end
282
293
 
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
- }
294
+ def response_body=(body)
295
+ super
296
+ response.close if response
293
297
  end
294
298
 
295
- def log_error(exception)
296
- logger = ActionController::Base.logger
297
- return unless logger
299
+ # Sends a stream to the browser, which is helpful when you're generating exports or other running data where you
300
+ # don't want the entire file buffered in memory first. Similar to send_data, but where the data is generated live.
301
+ #
302
+ # Options:
303
+ # * <tt>:filename</tt> - suggests a filename for the browser to use.
304
+ # * <tt>:type</tt> - specifies an HTTP content type.
305
+ # You can specify either a string or a symbol for a registered type with <tt>Mime::Type.register</tt>, for example :json.
306
+ # If omitted, type will be inferred from the file extension specified in <tt>:filename</tt>.
307
+ # If no content type is registered for the extension, the default type 'application/octet-stream' will be used.
308
+ # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
309
+ # Valid values are 'inline' and 'attachment' (default).
310
+ #
311
+ # Example of generating a csv export:
312
+ #
313
+ # send_stream(filename: "subscribers.csv") do |stream|
314
+ # stream.write "email_address,updated_at\n"
315
+ #
316
+ # @subscribers.find_each do |subscriber|
317
+ # stream.write "#{subscriber.email_address},#{subscriber.updated_at}\n"
318
+ # end
319
+ # end
320
+ def send_stream(filename:, disposition: "attachment", type: nil)
321
+ response.headers["Content-Type"] =
322
+ (type.is_a?(Symbol) ? Mime[type].to_s : type) ||
323
+ Mime::Type.lookup_by_extension(File.extname(filename).downcase.delete(".")) ||
324
+ "application/octet-stream"
325
+
326
+ response.headers["Content-Disposition"] =
327
+ ActionDispatch::Http::ContentDisposition.format(disposition: disposition, filename: filename)
328
+
329
+ yield response.stream
330
+ ensure
331
+ response.stream.close
332
+ end
298
333
 
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"
334
+ private
335
+ # Spawn a new thread to serve up the controller in. This is to get
336
+ # around the fact that Rack isn't based around IOs and we need to use
337
+ # a thread to stream data from the response bodies. Nobody should call
338
+ # this method except in Rails internals. Seriously!
339
+ def new_controller_thread # :nodoc:
340
+ Thread.new {
341
+ t2 = Thread.current
342
+ t2.abort_on_exception = true
343
+ yield
344
+ }
304
345
  end
305
- end
306
346
 
307
- def response_body=(body)
308
- super
309
- response.close if response
310
- end
347
+ def log_error(exception)
348
+ logger = ActionController::Base.logger
349
+ return unless logger
350
+
351
+ logger.fatal do
352
+ message = +"\n#{exception.class} (#{exception.message}):\n"
353
+ message << exception.annotated_source_code.to_s if exception.respond_to?(:annotated_source_code)
354
+ message << " " << exception.backtrace.join("\n ")
355
+ "#{message}\n\n"
356
+ end
357
+ end
311
358
  end
312
359
  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
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "abstract_controller/collector"
4
4
 
5
- module ActionController #:nodoc:
5
+ module ActionController # :nodoc:
6
6
  module MimeResponds
7
7
  # Without web-service support, an action which collects the data for displaying a list of people
8
8
  # might look something like this:
@@ -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
@@ -103,9 +103,9 @@ module ActionController #:nodoc:
103
103
  # If you need to use a MIME type which isn't supported by default, you can register your own handlers in
104
104
  # +config/initializers/mime_types.rb+ as follows.
105
105
  #
106
- # Mime::Type.register "image/jpg", :jpg
106
+ # Mime::Type.register "image/jpeg", :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
@@ -274,7 +289,7 @@ module ActionController #:nodoc:
274
289
  @format = request.negotiate_mime(@responses.keys)
275
290
  end
276
291
 
277
- class VariantCollector #:nodoc:
292
+ class VariantCollector # :nodoc:
278
293
  def initialize(variant = nil)
279
294
  @variant = variant
280
295
  @variants = {}
@@ -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
@@ -9,11 +9,14 @@ module ActionController
9
9
  # Wraps the parameters hash into a nested hash. This will allow clients to
10
10
  # submit requests without having to specify any root elements.
11
11
  #
12
- # This functionality is enabled in +config/initializers/wrap_parameters.rb+
13
- # and can be customized.
12
+ # This functionality is enabled by default for JSON, and can be customized by
13
+ # setting the format array:
14
14
  #
15
- # You could also turn it on per controller by setting the format array to
16
- # a non-empty array:
15
+ # class ApplicationController < ActionController::Base
16
+ # wrap_parameters format: [:json, :xml]
17
+ # end
18
+ #
19
+ # You could also turn it on per controller:
17
20
  #
18
21
  # class UsersController < ApplicationController
19
22
  # wrap_parameters format: [:json, :xml, :url_encoded_form, :multipart_form]
@@ -68,6 +71,12 @@ module ActionController
68
71
  # will try to check if <tt>Admin::User</tt> or +User+ model exists, and use it to
69
72
  # determine the wrapper key respectively. If both models don't exist,
70
73
  # it will then fallback to use +user+ as the key.
74
+ #
75
+ # To disable this functionality for a controller:
76
+ #
77
+ # class UsersController < ApplicationController
78
+ # wrap_parameters false
79
+ # end
71
80
  module ParamsWrapper
72
81
  extend ActiveSupport::Concern
73
82
 
@@ -93,7 +102,7 @@ module ActionController
93
102
  end
94
103
 
95
104
  def model
96
- super || synchronize { super || self.model = _default_wrap_model }
105
+ super || self.model = _default_wrap_model
97
106
  end
98
107
 
99
108
  def include
@@ -107,15 +116,19 @@ module ActionController
107
116
 
108
117
  unless super || exclude
109
118
  if m.respond_to?(:attribute_names) && m.attribute_names.any?
119
+ self.include = m.attribute_names
120
+
110
121
  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
122
+ self.include += m.stored_attributes.values.flatten.map(&:to_s)
123
+ end
124
+
125
+ if m.respond_to?(:attribute_aliases) && m.attribute_aliases.any?
126
+ self.include += m.attribute_aliases.keys
114
127
  end
115
128
 
116
129
  if m.respond_to?(:nested_attributes_options) && m.nested_attributes_options.keys.any?
117
130
  self.include += m.nested_attributes_options.keys.map do |key|
118
- key.to_s.concat("_attributes")
131
+ (+key.to_s).concat("_attributes")
119
132
  end
120
133
  end
121
134
 
@@ -151,7 +164,7 @@ module ActionController
151
164
  # try to find Foo::Bar::User, Foo::User and finally User.
152
165
  def _default_wrap_model
153
166
  return nil if klass.anonymous?
154
- model_name = klass.name.sub(/Controller$/, "").classify
167
+ model_name = klass.name.delete_suffix("Controller").classify
155
168
 
156
169
  begin
157
170
  if model_klass = model_name.safe_constantize
@@ -189,7 +202,7 @@ module ActionController
189
202
  #
190
203
  # wrap_parameters Person
191
204
  # # wraps parameters by determining the wrapper key from Person class
192
- # (+person+, in this case) and the list of attribute names
205
+ # # (+person+, in this case) and the list of attribute names
193
206
  #
194
207
  # wrap_parameters include: [:username, :title]
195
208
  # # wraps only +:username+ and +:title+ attributes from parameters.
@@ -238,25 +251,13 @@ module ActionController
238
251
  end
239
252
  end
240
253
 
241
- # Performs parameters wrapping upon the request. Called automatically
242
- # 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
256
- super
257
- end
258
-
259
254
  private
255
+ # Performs parameters wrapping upon the request. Called automatically
256
+ # by the metal call stack.
257
+ def process_action(*)
258
+ _perform_parameter_wrapping if _wrapper_enabled?
259
+ super
260
+ end
260
261
 
261
262
  # Returns the wrapper key which will be used to store wrapped parameters.
262
263
  def _wrapper_key
@@ -276,9 +277,11 @@ module ActionController
276
277
  def _extract_parameters(parameters)
277
278
  if include_only = _wrapper_options.include
278
279
  parameters.slice(*include_only)
280
+ elsif _wrapper_options.exclude
281
+ exclude = _wrapper_options.exclude + EXCLUDE_PARAMETERS
282
+ parameters.except(*exclude)
279
283
  else
280
- exclude = _wrapper_options.exclude || []
281
- parameters.except(*(exclude + EXCLUDE_PARAMETERS))
284
+ parameters.except(*EXCLUDE_PARAMETERS)
282
285
  end
283
286
  end
284
287
 
@@ -287,7 +290,23 @@ module ActionController
287
290
  return false unless request.has_content_type?
288
291
 
289
292
  ref = request.content_mime_type.ref
293
+
290
294
  _wrapper_formats.include?(ref) && _wrapper_key && !request.parameters.key?(_wrapper_key)
295
+ rescue ActionDispatch::Http::Parameters::ParseError
296
+ false
297
+ end
298
+
299
+ def _perform_parameter_wrapping
300
+ wrapped_hash = _wrap_parameters request.request_parameters
301
+ wrapped_keys = request.request_parameters.keys
302
+ wrapped_filtered_hash = _wrap_parameters request.filtered_parameters.slice(*wrapped_keys)
303
+
304
+ # This will make the wrapped hash accessible from controller and view.
305
+ request.parameters.merge! wrapped_hash
306
+ request.request_parameters.merge! wrapped_hash
307
+
308
+ # This will display the wrapped hash in the log file.
309
+ request.filtered_parameters.merge! wrapped_filtered_hash
291
310
  end
292
311
  end
293
312
  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