actionpack 4.1.16 → 4.2.0.beta1

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 (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +163 -690
  3. data/README.rdoc +7 -2
  4. data/lib/abstract_controller/base.rb +16 -6
  5. data/lib/abstract_controller/callbacks.rb +28 -51
  6. data/lib/abstract_controller/helpers.rb +0 -3
  7. data/lib/abstract_controller/railties/routes_helpers.rb +3 -3
  8. data/lib/abstract_controller/rendering.rb +1 -7
  9. data/lib/abstract_controller/url_for.rb +1 -1
  10. data/lib/action_controller.rb +1 -0
  11. data/lib/action_controller/base.rb +2 -1
  12. data/lib/action_controller/caching.rb +1 -1
  13. data/lib/action_controller/caching/fragments.rb +7 -1
  14. data/lib/action_controller/log_subscriber.rb +26 -25
  15. data/lib/action_controller/metal.rb +11 -7
  16. data/lib/action_controller/metal/conditional_get.rb +31 -6
  17. data/lib/action_controller/metal/etag_with_template_digest.rb +50 -0
  18. data/lib/action_controller/metal/force_ssl.rb +1 -1
  19. data/lib/action_controller/metal/head.rb +2 -0
  20. data/lib/action_controller/metal/http_authentication.rb +3 -15
  21. data/lib/action_controller/metal/instrumentation.rb +4 -7
  22. data/lib/action_controller/metal/live.rb +57 -6
  23. data/lib/action_controller/metal/mime_responds.rb +17 -227
  24. data/lib/action_controller/metal/redirecting.rb +14 -8
  25. data/lib/action_controller/metal/renderers.rb +19 -3
  26. data/lib/action_controller/metal/rendering.rb +2 -6
  27. data/lib/action_controller/metal/request_forgery_protection.rb +75 -7
  28. data/lib/action_controller/metal/streaming.rb +1 -1
  29. data/lib/action_controller/metal/strong_parameters.rb +111 -11
  30. data/lib/action_controller/metal/url_for.rb +11 -12
  31. data/lib/action_controller/model_naming.rb +1 -1
  32. data/lib/action_controller/railtie.rb +4 -0
  33. data/lib/action_controller/test_case.rb +87 -75
  34. data/lib/action_dispatch/http/cache.rb +1 -1
  35. data/lib/action_dispatch/http/filter_parameters.rb +2 -2
  36. data/lib/action_dispatch/http/headers.rb +43 -9
  37. data/lib/action_dispatch/http/mime_negotiation.rb +10 -4
  38. data/lib/action_dispatch/http/mime_type.rb +2 -16
  39. data/lib/action_dispatch/http/parameter_filter.rb +1 -1
  40. data/lib/action_dispatch/http/parameters.rb +11 -26
  41. data/lib/action_dispatch/http/request.rb +30 -10
  42. data/lib/action_dispatch/http/response.rb +52 -17
  43. data/lib/action_dispatch/http/upload.rb +3 -8
  44. data/lib/action_dispatch/http/url.rb +87 -70
  45. data/lib/action_dispatch/journey/formatter.rb +18 -17
  46. data/lib/action_dispatch/journey/gtg/builder.rb +3 -3
  47. data/lib/action_dispatch/journey/gtg/simulator.rb +10 -7
  48. data/lib/action_dispatch/journey/gtg/transition_table.rb +18 -26
  49. data/lib/action_dispatch/journey/nfa/dot.rb +2 -2
  50. data/lib/action_dispatch/journey/nfa/simulator.rb +1 -1
  51. data/lib/action_dispatch/journey/nfa/transition_table.rb +5 -5
  52. data/lib/action_dispatch/journey/nodes/node.rb +4 -0
  53. data/lib/action_dispatch/journey/parser.rb +52 -60
  54. data/lib/action_dispatch/journey/parser.y +11 -10
  55. data/lib/action_dispatch/journey/path/pattern.rb +16 -19
  56. data/lib/action_dispatch/journey/route.rb +3 -18
  57. data/lib/action_dispatch/journey/router.rb +34 -65
  58. data/lib/action_dispatch/journey/router/strexp.rb +9 -6
  59. data/lib/action_dispatch/journey/routes.rb +0 -4
  60. data/lib/action_dispatch/journey/visitors.rb +81 -92
  61. data/lib/action_dispatch/journey/visualizer/index.html.erb +2 -2
  62. data/lib/action_dispatch/middleware/cookies.rb +27 -31
  63. data/lib/action_dispatch/middleware/debug_exceptions.rb +32 -3
  64. data/lib/action_dispatch/middleware/exception_wrapper.rb +19 -17
  65. data/lib/action_dispatch/middleware/flash.rb +7 -4
  66. data/lib/action_dispatch/middleware/public_exceptions.rb +13 -8
  67. data/lib/action_dispatch/middleware/remote_ip.rb +3 -3
  68. data/lib/action_dispatch/middleware/request_id.rb +1 -1
  69. data/lib/action_dispatch/middleware/session/cookie_store.rb +1 -1
  70. data/lib/action_dispatch/middleware/show_exceptions.rb +1 -0
  71. data/lib/action_dispatch/middleware/static.rb +22 -23
  72. data/lib/action_dispatch/middleware/templates/rescues/_source.erb +22 -18
  73. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +36 -8
  74. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +2 -8
  75. data/lib/action_dispatch/middleware/templates/rescues/{diagnostics.erb → diagnostics.html.erb} +0 -0
  76. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  77. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +6 -0
  78. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -24
  79. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +0 -1
  80. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +119 -63
  81. data/lib/action_dispatch/routing/endpoint.rb +10 -0
  82. data/lib/action_dispatch/routing/inspector.rb +4 -11
  83. data/lib/action_dispatch/routing/mapper.rb +399 -278
  84. data/lib/action_dispatch/routing/polymorphic_routes.rb +190 -78
  85. data/lib/action_dispatch/routing/redirection.rb +10 -12
  86. data/lib/action_dispatch/routing/route_set.rb +224 -177
  87. data/lib/action_dispatch/routing/url_for.rb +9 -4
  88. data/lib/action_dispatch/testing/assertions.rb +11 -7
  89. data/lib/action_dispatch/testing/assertions/dom.rb +2 -26
  90. data/lib/action_dispatch/testing/assertions/response.rb +2 -7
  91. data/lib/action_dispatch/testing/assertions/routing.rb +9 -9
  92. data/lib/action_dispatch/testing/assertions/selector.rb +2 -429
  93. data/lib/action_dispatch/testing/assertions/tag.rb +2 -134
  94. data/lib/action_dispatch/testing/integration.rb +15 -18
  95. data/lib/action_dispatch/testing/test_request.rb +1 -1
  96. data/lib/action_dispatch/testing/test_response.rb +5 -1
  97. data/lib/action_pack/gem_version.rb +3 -3
  98. metadata +57 -15
  99. data/lib/action_controller/metal/responder.rb +0 -297
@@ -14,7 +14,7 @@ module ActionController
14
14
  include ActionController::RackDelegation
15
15
  include ActionController::UrlFor
16
16
 
17
- # Redirects the browser to the target specified in +options+. This parameter can take one of three forms:
17
+ # Redirects the browser to the target specified in +options+. This parameter can be any one of:
18
18
  #
19
19
  # * <tt>Hash</tt> - The URL will be generated by calling url_for with the +options+.
20
20
  # * <tt>Record</tt> - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record.
@@ -24,6 +24,8 @@ module ActionController
24
24
  # * <tt>:back</tt> - Back to the page that issued the request. Useful for forms that are triggered from multiple places.
25
25
  # Short-hand for <tt>redirect_to(request.env["HTTP_REFERER"])</tt>
26
26
  #
27
+ # === Examples:
28
+ #
27
29
  # redirect_to action: "show", id: 5
28
30
  # redirect_to post
29
31
  # redirect_to "http://www.rubyonrails.org"
@@ -32,7 +34,7 @@ module ActionController
32
34
  # redirect_to :back
33
35
  # redirect_to proc { edit_post_url(@post) }
34
36
  #
35
- # The redirection happens as a "302 Found" header unless otherwise specified.
37
+ # The redirection happens as a "302 Found" header unless otherwise specified using the <tt>:status</tt> option:
36
38
  #
37
39
  # redirect_to post_url(@post), status: :found
38
40
  # redirect_to action: 'atom', status: :moved_permanently
@@ -60,19 +62,21 @@ module ActionController
60
62
  # redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id }
61
63
  # redirect_to({ action: 'atom' }, alert: "Something serious happened")
62
64
  #
63
- # When using <tt>redirect_to :back</tt>, if there is no referrer, ActionController::RedirectBackError will be raised. You may specify some fallback
64
- # behavior for this case by rescuing ActionController::RedirectBackError.
65
+ # When using <tt>redirect_to :back</tt>, if there is no referrer,
66
+ # <tt>ActionController::RedirectBackError</tt> will be raised. You
67
+ # may specify some fallback behavior for this case by rescuing
68
+ # <tt>ActionController::RedirectBackError</tt>.
65
69
  def redirect_to(options = {}, response_status = {}) #:doc:
66
70
  raise ActionControllerError.new("Cannot redirect to nil!") unless options
67
71
  raise ActionControllerError.new("Cannot redirect to a parameter hash!") if options.is_a?(ActionController::Parameters)
68
72
  raise AbstractController::DoubleRenderError if response_body
69
73
 
70
74
  self.status = _extract_redirect_to_status(options, response_status)
71
- self.location = _compute_redirect_to_location(options)
72
- self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.h(location)}\">redirected</a>.</body></html>"
75
+ self.location = _compute_redirect_to_location(request, options)
76
+ self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(location)}\">redirected</a>.</body></html>"
73
77
  end
74
78
 
75
- def _compute_redirect_to_location(options) #:nodoc:
79
+ def _compute_redirect_to_location(request, options) #:nodoc:
76
80
  case options
77
81
  # The scheme name consist of a letter followed by any combination of
78
82
  # letters, digits, and the plus ("+"), period ("."), or hyphen ("-")
@@ -86,11 +90,13 @@ module ActionController
86
90
  when :back
87
91
  request.headers["Referer"] or raise RedirectBackError
88
92
  when Proc
89
- _compute_redirect_to_location options.call
93
+ _compute_redirect_to_location request, options.call
90
94
  else
91
95
  url_for(options)
92
96
  end.delete("\0\r\n")
93
97
  end
98
+ module_function :_compute_redirect_to_location
99
+ public :_compute_redirect_to_location
94
100
 
95
101
  private
96
102
  def _extract_redirect_to_status(options, response_status)
@@ -6,6 +6,11 @@ module ActionController
6
6
  Renderers.add(key, &block)
7
7
  end
8
8
 
9
+ # See <tt>Renderers.remove</tt>
10
+ def self.remove_renderer(key)
11
+ Renderers.remove(key)
12
+ end
13
+
9
14
  class MissingRenderer < LoadError
10
15
  def initialize(format)
11
16
  super "No renderer defined for format: #{format}"
@@ -42,8 +47,8 @@ module ActionController
42
47
  nil
43
48
  end
44
49
 
45
- # Hash of available renderers, mapping a renderer name to its proc.
46
- # Default keys are <tt>:json</tt>, <tt>:js</tt>, <tt>:xml</tt>.
50
+ # A Set containing renderer names that correspond to available renderer procs.
51
+ # Default values are <tt>:json</tt>, <tt>:js</tt>, <tt>:xml</tt>.
47
52
  RENDERERS = Set.new
48
53
 
49
54
  # Adds a new renderer to call within controller actions.
@@ -73,7 +78,7 @@ module ActionController
73
78
  # respond_to do |format|
74
79
  # format.html
75
80
  # format.csv { render csv: @csvable, filename: @csvable.name }
76
- # }
81
+ # end
77
82
  # end
78
83
  # To use renderers and their mime types in more concise ways, see
79
84
  # <tt>ActionController::MimeResponds::ClassMethods.respond_to</tt> and
@@ -83,6 +88,17 @@ module ActionController
83
88
  RENDERERS << key.to_sym
84
89
  end
85
90
 
91
+ # This method is the opposite of add method.
92
+ #
93
+ # Usage:
94
+ #
95
+ # ActionController::Renderers.remove(:csv)
96
+ def self.remove(key)
97
+ RENDERERS.delete(key.to_sym)
98
+ method = "_render_option_#{key}"
99
+ remove_method(method) if method_defined?(method)
100
+ end
101
+
86
102
  module All
87
103
  extend ActiveSupport::Concern
88
104
  include Renderers
@@ -67,8 +67,8 @@ module ActionController
67
67
  options[:html] = ERB::Util.html_escape(options[:html])
68
68
  end
69
69
 
70
- if options.delete(:nothing) || _any_render_format_is_nil?(options)
71
- options[:body] = " "
70
+ if options.delete(:nothing)
71
+ options[:body] = nil
72
72
  end
73
73
 
74
74
  if options[:status]
@@ -86,10 +86,6 @@ module ActionController
86
86
  end
87
87
  end
88
88
 
89
- def _any_render_format_is_nil?(options)
90
- RENDER_FORMATS_IN_PRIORITY.any? { |format| options.key?(format) && options[format].nil? }
91
- end
92
-
93
89
  # Process controller specific options, as status, content-type and location.
94
90
  def _process_options(options) #:nodoc:
95
91
  status, content_type, location = options.values_at(:status, :content_type, :location)
@@ -9,7 +9,7 @@ module ActionController #:nodoc:
9
9
  end
10
10
 
11
11
  # Controller actions are protected from Cross-Site Request Forgery (CSRF) attacks
12
- # by including a token in the rendered html for your application. This token is
12
+ # by including a token in the rendered HTML for your application. This token is
13
13
  # stored as a random string in the session, to which an attacker does not have
14
14
  # access. When a request reaches your application, \Rails verifies the received
15
15
  # token with the token in the session. Only HTML and JavaScript requests are checked,
@@ -44,7 +44,7 @@ module ActionController #:nodoc:
44
44
  #
45
45
  # The token parameter is named <tt>authenticity_token</tt> by default. The name and
46
46
  # value of this token must be added to every layout that renders forms by including
47
- # <tt>csrf_meta_tags</tt> in the html +head+.
47
+ # <tt>csrf_meta_tags</tt> in the HTML +head+.
48
48
  #
49
49
  # Learn more about CSRF attacks and securing your application in the
50
50
  # {Ruby on Rails Security Guide}[http://guides.rubyonrails.org/security.html].
@@ -68,12 +68,16 @@ module ActionController #:nodoc:
68
68
  config_accessor :allow_forgery_protection
69
69
  self.allow_forgery_protection = true if allow_forgery_protection.nil?
70
70
 
71
+ # Controls whether a CSRF failure logs a warning. On by default.
72
+ config_accessor :log_warning_on_csrf_failure
73
+ self.log_warning_on_csrf_failure = true
74
+
71
75
  helper_method :form_authenticity_token
72
76
  helper_method :protect_against_forgery?
73
77
  end
74
78
 
75
79
  module ClassMethods
76
- # Turn on request forgery protection. Bear in mind that only non-GET, HTML/JavaScript requests are checked.
80
+ # Turn on request forgery protection. Bear in mind that GET and HEAD requests are not checked.
77
81
  #
78
82
  # class ApplicationController < ActionController::Base
79
83
  # protect_from_forgery
@@ -193,7 +197,9 @@ module ActionController #:nodoc:
193
197
  mark_for_same_origin_verification!
194
198
 
195
199
  if !verified_request?
196
- logger.warn "Can't verify CSRF token authenticity" if logger
200
+ if logger && log_warning_on_csrf_failure
201
+ logger.warn "Can't verify CSRF token authenticity"
202
+ end
197
203
  handle_unverified_request
198
204
  end
199
205
  end
@@ -234,6 +240,8 @@ module ActionController #:nodoc:
234
240
  content_type =~ %r(\Atext/javascript) && !request.xhr?
235
241
  end
236
242
 
243
+ AUTHENTICITY_TOKEN_LENGTH = 32
244
+
237
245
  # Returns true or false if a request is verified. Checks:
238
246
  #
239
247
  # * is it a GET or HEAD request? Gets should be safe and idempotent
@@ -241,13 +249,73 @@ module ActionController #:nodoc:
241
249
  # * Does the X-CSRF-Token header match the form_authenticity_token
242
250
  def verified_request?
243
251
  !protect_against_forgery? || request.get? || request.head? ||
244
- form_authenticity_token == params[request_forgery_protection_token] ||
245
- form_authenticity_token == request.headers['X-CSRF-Token']
252
+ valid_authenticity_token?(session, form_authenticity_param) ||
253
+ valid_authenticity_token?(session, request.headers['X-CSRF-Token'])
246
254
  end
247
255
 
248
256
  # Sets the token value for the current session.
249
257
  def form_authenticity_token
250
- session[:_csrf_token] ||= SecureRandom.base64(32)
258
+ masked_authenticity_token(session)
259
+ end
260
+
261
+ # Creates a masked version of the authenticity token that varies
262
+ # on each request. The masking is used to mitigate SSL attacks
263
+ # like BREACH.
264
+ def masked_authenticity_token(session)
265
+ one_time_pad = SecureRandom.random_bytes(AUTHENTICITY_TOKEN_LENGTH)
266
+ encrypted_csrf_token = xor_byte_strings(one_time_pad, real_csrf_token(session))
267
+ masked_token = one_time_pad + encrypted_csrf_token
268
+ Base64.strict_encode64(masked_token)
269
+ end
270
+
271
+ # Checks the client's masked token to see if it matches the
272
+ # session token. Essentially the inverse of
273
+ # +masked_authenticity_token+.
274
+ def valid_authenticity_token?(session, encoded_masked_token)
275
+ return false if encoded_masked_token.nil? || encoded_masked_token.empty?
276
+
277
+ begin
278
+ masked_token = Base64.strict_decode64(encoded_masked_token)
279
+ rescue ArgumentError # encoded_masked_token is invalid Base64
280
+ return false
281
+ end
282
+
283
+ # See if it's actually a masked token or not. In order to
284
+ # deploy this code, we should be able to handle any unmasked
285
+ # tokens that we've issued without error.
286
+
287
+ if masked_token.length == AUTHENTICITY_TOKEN_LENGTH
288
+ # This is actually an unmasked token. This is expected if
289
+ # you have just upgraded to masked tokens, but should stop
290
+ # happening shortly after installing this gem
291
+ compare_with_real_token masked_token, session
292
+
293
+ elsif masked_token.length == AUTHENTICITY_TOKEN_LENGTH * 2
294
+ # Split the token into the one-time pad and the encrypted
295
+ # value and decrypt it
296
+ one_time_pad = masked_token[0...AUTHENTICITY_TOKEN_LENGTH]
297
+ encrypted_csrf_token = masked_token[AUTHENTICITY_TOKEN_LENGTH..-1]
298
+ csrf_token = xor_byte_strings(one_time_pad, encrypted_csrf_token)
299
+
300
+ compare_with_real_token csrf_token, session
301
+
302
+ else
303
+ false # Token is malformed
304
+ end
305
+ end
306
+
307
+ def compare_with_real_token(token, session)
308
+ # Borrow a constant-time comparison from Rack
309
+ Rack::Utils.secure_compare(token, real_csrf_token(session))
310
+ end
311
+
312
+ def real_csrf_token(session)
313
+ session[:_csrf_token] ||= SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
314
+ Base64.strict_decode64(session[:_csrf_token])
315
+ end
316
+
317
+ def xor_byte_strings(s1, s2)
318
+ s1.bytes.zip(s2.bytes).map { |(c1,c2)| c1 ^ c2 }.pack('c*')
251
319
  end
252
320
 
253
321
  # The form's authenticity parameter. Override to provide your own.
@@ -183,7 +183,7 @@ module ActionController #:nodoc:
183
183
  # You may also want to configure other parameters like <tt>:tcp_nodelay</tt>.
184
184
  # Please check its documentation for more information: http://unicorn.bogomips.org/Unicorn/Configurator.html#method-i-listen
185
185
  #
186
- # If you are using Unicorn with Nginx, you may need to tweak Nginx.
186
+ # If you are using Unicorn with NGINX, you may need to tweak NGINX.
187
187
  # Streaming should work out of the box on Rainbows.
188
188
  #
189
189
  # ==== Passenger
@@ -1,5 +1,6 @@
1
1
  require 'active_support/core_ext/hash/indifferent_access'
2
2
  require 'active_support/core_ext/array/wrap'
3
+ require 'active_support/deprecation'
3
4
  require 'active_support/rescuable'
4
5
  require 'action_dispatch/http/upload'
5
6
  require 'stringio'
@@ -32,14 +33,14 @@ module ActionController
32
33
 
33
34
  def initialize(params) # :nodoc:
34
35
  @params = params
35
- super("found unpermitted parameters: #{params.join(", ")}")
36
+ super("found unpermitted parameter#{'s' if params.size > 1 }: #{params.join(", ")}")
36
37
  end
37
38
  end
38
39
 
39
40
  # == Action Controller \Parameters
40
41
  #
41
42
  # Allows to choose which attributes should be whitelisted for mass updating
42
- # and thus prevent accidentally exposing that which shouldnt be exposed.
43
+ # and thus prevent accidentally exposing that which shouldn't be exposed.
43
44
  # Provides two methods for this purpose: #require and #permit. The former is
44
45
  # used to mark parameters as required. The latter is used to set the parameter
45
46
  # as permitted and limit which attributes should be allowed for mass updating.
@@ -101,9 +102,23 @@ module ActionController
101
102
  cattr_accessor :permit_all_parameters, instance_accessor: false
102
103
  cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false
103
104
 
104
- # Never raise an UnpermittedParameters exception because of these params
105
- # are present. They are added by Rails and it's of no concern.
106
- NEVER_UNPERMITTED_PARAMS = %w( controller action )
105
+ # By default, never raise an UnpermittedParameters exception if these
106
+ # params are present. The default includes both 'controller' and 'action'
107
+ # because they are added by Rails and should be of no concern. One way
108
+ # to change these is to specify `always_permitted_parameters` in your
109
+ # config. For instance:
110
+ #
111
+ # config.always_permitted_parameters = %w( controller action format )
112
+ cattr_accessor :always_permitted_parameters
113
+ self.always_permitted_parameters = %w( controller action )
114
+
115
+ def self.const_missing(const_name)
116
+ super unless const_name == :NEVER_UNPERMITTED_PARAMS
117
+ ActiveSupport::Deprecation.warn "`ActionController::Parameters::NEVER_UNPERMITTED_PARAMS`"\
118
+ " has been deprecated. Use "\
119
+ "`ActionController::Parameters.always_permitted_parameters` instead."
120
+ self.always_permitted_parameters
121
+ end
107
122
 
108
123
  # Returns a new instance of <tt>ActionController::Parameters</tt>.
109
124
  # Also, sets the +permitted+ attribute to the default value of
@@ -126,9 +141,44 @@ module ActionController
126
141
  @permitted = self.class.permit_all_parameters
127
142
  end
128
143
 
144
+ # Returns a safe +Hash+ representation of this parameter with all
145
+ # unpermitted keys removed.
146
+ #
147
+ # params = ActionController::Parameters.new({
148
+ # name: 'Senjougahara Hitagi',
149
+ # oddity: 'Heavy stone crab'
150
+ # })
151
+ # params.to_h # => {}
152
+ #
153
+ # safe_params = params.permit(:name)
154
+ # safe_params.to_h # => {"name"=>"Senjougahara Hitagi"}
155
+ def to_h
156
+ if permitted?
157
+ to_hash
158
+ else
159
+ slice(*self.class.always_permitted_parameters).permit!.to_h
160
+ end
161
+ end
162
+
163
+ # Convert all hashes in values into parameters, then yield each pair like
164
+ # the same way as <tt>Hash#each_pair</tt>
165
+ def each_pair(&block)
166
+ super do |key, value|
167
+ convert_hashes_to_parameters(key, value)
168
+ end
169
+
170
+ super
171
+ end
172
+
173
+ alias_method :each, :each_pair
174
+
129
175
  # Attribute that keeps track of converted arrays, if any, to avoid double
130
176
  # looping in the common use case permit + mass-assignment. Defined in a
131
177
  # method to instantiate it only if needed.
178
+ #
179
+ # Testing membership still loops, but it's going to be faster than our own
180
+ # loop that converts values. Also, we are not going to build a new array
181
+ # object per fetch.
132
182
  def converted_arrays
133
183
  @converted_arrays ||= Set.new
134
184
  end
@@ -157,9 +207,8 @@ module ActionController
157
207
  # Person.new(params) # => #<Person id: nil, name: "Francesco">
158
208
  def permit!
159
209
  each_pair do |key, value|
160
- value = convert_hashes_to_parameters(key, value)
161
- Array.wrap(value).each do |_|
162
- _.permit! if _.respond_to? :permit!
210
+ Array.wrap(value).each do |v|
211
+ v.permit! if v.respond_to? :permit!
163
212
  end
164
213
  end
165
214
 
@@ -312,11 +361,56 @@ module ActionController
312
361
  # params.slice(:a, :b) # => {"a"=>1, "b"=>2}
313
362
  # params.slice(:d) # => {}
314
363
  def slice(*keys)
315
- self.class.new(super).tap do |new_instance|
316
- new_instance.permitted = @permitted
364
+ new_instance_with_inherited_permitted_status(super)
365
+ end
366
+
367
+ # Removes and returns the key/value pairs matching the given keys.
368
+ #
369
+ # params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
370
+ # params.extract!(:a, :b) # => {"a"=>1, "b"=>2}
371
+ # params # => {"c"=>3}
372
+ def extract!(*keys)
373
+ new_instance_with_inherited_permitted_status(super)
374
+ end
375
+
376
+ # Returns a new <tt>ActionController::Parameters</tt> with the results of
377
+ # running +block+ once for every value. The keys are unchanged.
378
+ #
379
+ # params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
380
+ # params.transform_values { |x| x * 2 }
381
+ # # => {"a"=>2, "b"=>4, "c"=>6}
382
+ def transform_values
383
+ if block_given?
384
+ new_instance_with_inherited_permitted_status(super)
385
+ else
386
+ super
387
+ end
388
+ end
389
+
390
+ # This method is here only to make sure that the returned object has the
391
+ # correct +permitted+ status. It should not matter since the parent of
392
+ # this object is +HashWithIndifferentAccess+
393
+ def transform_keys # :nodoc:
394
+ if block_given?
395
+ new_instance_with_inherited_permitted_status(super)
396
+ else
397
+ super
317
398
  end
318
399
  end
319
400
 
401
+ # Deletes and returns a key-value pair from +Parameters+ whose key is equal
402
+ # to key. If the key is not found, returns the default value. If the
403
+ # optional code block is given and the key is not found, pass in the key
404
+ # and return the result of block.
405
+ def delete(key, &block)
406
+ convert_hashes_to_parameters(key, super, false)
407
+ end
408
+
409
+ # Equivalent to Hash#keep_if, but returns nil if no changes were made.
410
+ def select!(&block)
411
+ convert_value_to_parameters(super)
412
+ end
413
+
320
414
  # Returns an exact copy of the <tt>ActionController::Parameters</tt>
321
415
  # instance. +permitted+ state is kept on the duped object.
322
416
  #
@@ -337,6 +431,12 @@ module ActionController
337
431
  end
338
432
 
339
433
  private
434
+ def new_instance_with_inherited_permitted_status(hash)
435
+ self.class.new(hash).tap do |new_instance|
436
+ new_instance.permitted = @permitted
437
+ end
438
+ end
439
+
340
440
  def convert_hashes_to_parameters(key, value, assign_if_converted=true)
341
441
  converted = convert_value_to_parameters(value)
342
442
  self[key] = converted if assign_if_converted && !converted.equal?(value)
@@ -385,7 +485,7 @@ module ActionController
385
485
  end
386
486
 
387
487
  def unpermitted_keys(params)
388
- self.keys - params.keys - NEVER_UNPERMITTED_PARAMS
488
+ self.keys - params.keys - self.always_permitted_parameters
389
489
  end
390
490
 
391
491
  #