actionpack 6.0.3.7 → 6.1.3.2

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 (118) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +287 -226
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/abstract_controller.rb +1 -0
  6. data/lib/abstract_controller/base.rb +35 -2
  7. data/lib/abstract_controller/callbacks.rb +2 -2
  8. data/lib/abstract_controller/helpers.rb +105 -90
  9. data/lib/abstract_controller/railties/routes_helpers.rb +17 -1
  10. data/lib/abstract_controller/rendering.rb +9 -9
  11. data/lib/abstract_controller/translation.rb +8 -2
  12. data/lib/action_controller.rb +2 -3
  13. data/lib/action_controller/api.rb +2 -2
  14. data/lib/action_controller/base.rb +4 -2
  15. data/lib/action_controller/caching.rb +0 -1
  16. data/lib/action_controller/log_subscriber.rb +3 -3
  17. data/lib/action_controller/metal.rb +2 -2
  18. data/lib/action_controller/metal/conditional_get.rb +11 -3
  19. data/lib/action_controller/metal/content_security_policy.rb +1 -1
  20. data/lib/action_controller/metal/cookies.rb +3 -1
  21. data/lib/action_controller/metal/data_streaming.rb +1 -1
  22. data/lib/action_controller/metal/etag_with_template_digest.rb +2 -4
  23. data/lib/action_controller/metal/exceptions.rb +33 -0
  24. data/lib/action_controller/metal/head.rb +7 -4
  25. data/lib/action_controller/metal/helpers.rb +11 -1
  26. data/lib/action_controller/metal/http_authentication.rb +4 -2
  27. data/lib/action_controller/metal/implicit_render.rb +1 -1
  28. data/lib/action_controller/metal/instrumentation.rb +11 -9
  29. data/lib/action_controller/metal/live.rb +1 -1
  30. data/lib/action_controller/metal/logging.rb +20 -0
  31. data/lib/action_controller/metal/mime_responds.rb +6 -2
  32. data/lib/action_controller/metal/parameter_encoding.rb +35 -4
  33. data/lib/action_controller/metal/params_wrapper.rb +14 -8
  34. data/lib/action_controller/metal/permissions_policy.rb +46 -0
  35. data/lib/action_controller/metal/redirecting.rb +1 -1
  36. data/lib/action_controller/metal/rendering.rb +6 -0
  37. data/lib/action_controller/metal/request_forgery_protection.rb +48 -24
  38. data/lib/action_controller/metal/rescue.rb +1 -1
  39. data/lib/action_controller/metal/strong_parameters.rb +103 -15
  40. data/lib/action_controller/renderer.rb +24 -13
  41. data/lib/action_controller/test_case.rb +62 -56
  42. data/lib/action_dispatch.rb +3 -2
  43. data/lib/action_dispatch/http/cache.rb +12 -10
  44. data/lib/action_dispatch/http/content_disposition.rb +2 -2
  45. data/lib/action_dispatch/http/content_security_policy.rb +5 -1
  46. data/lib/action_dispatch/http/filter_parameters.rb +1 -1
  47. data/lib/action_dispatch/http/filter_redirect.rb +1 -1
  48. data/lib/action_dispatch/http/headers.rb +3 -2
  49. data/lib/action_dispatch/http/mime_negotiation.rb +20 -8
  50. data/lib/action_dispatch/http/mime_type.rb +29 -16
  51. data/lib/action_dispatch/http/parameters.rb +1 -19
  52. data/lib/action_dispatch/http/permissions_policy.rb +173 -0
  53. data/lib/action_dispatch/http/request.rb +26 -8
  54. data/lib/action_dispatch/http/response.rb +17 -16
  55. data/lib/action_dispatch/http/url.rb +3 -2
  56. data/lib/action_dispatch/journey.rb +0 -2
  57. data/lib/action_dispatch/journey/formatter.rb +53 -28
  58. data/lib/action_dispatch/journey/gtg/builder.rb +22 -36
  59. data/lib/action_dispatch/journey/gtg/simulator.rb +8 -7
  60. data/lib/action_dispatch/journey/gtg/transition_table.rb +6 -4
  61. data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
  62. data/lib/action_dispatch/journey/nodes/node.rb +4 -3
  63. data/lib/action_dispatch/journey/parser.rb +13 -13
  64. data/lib/action_dispatch/journey/parser.y +1 -1
  65. data/lib/action_dispatch/journey/path/pattern.rb +13 -18
  66. data/lib/action_dispatch/journey/route.rb +7 -18
  67. data/lib/action_dispatch/journey/router.rb +26 -30
  68. data/lib/action_dispatch/journey/router/utils.rb +6 -4
  69. data/lib/action_dispatch/middleware/actionable_exceptions.rb +2 -2
  70. data/lib/action_dispatch/middleware/cookies.rb +74 -33
  71. data/lib/action_dispatch/middleware/debug_exceptions.rb +10 -17
  72. data/lib/action_dispatch/middleware/debug_view.rb +1 -1
  73. data/lib/action_dispatch/middleware/exception_wrapper.rb +29 -17
  74. data/lib/action_dispatch/middleware/host_authorization.rb +27 -7
  75. data/lib/action_dispatch/middleware/public_exceptions.rb +1 -1
  76. data/lib/action_dispatch/middleware/remote_ip.rb +5 -4
  77. data/lib/action_dispatch/middleware/request_id.rb +4 -5
  78. data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -2
  79. data/lib/action_dispatch/middleware/session/cookie_store.rb +2 -2
  80. data/lib/action_dispatch/middleware/show_exceptions.rb +2 -0
  81. data/lib/action_dispatch/middleware/ssl.rb +12 -7
  82. data/lib/action_dispatch/middleware/stack.rb +19 -1
  83. data/lib/action_dispatch/middleware/static.rb +154 -93
  84. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  85. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +2 -5
  86. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +2 -2
  87. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +2 -2
  88. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +100 -8
  89. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  90. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +21 -1
  91. data/lib/action_dispatch/railtie.rb +3 -2
  92. data/lib/action_dispatch/request/session.rb +2 -8
  93. data/lib/action_dispatch/request/utils.rb +26 -2
  94. data/lib/action_dispatch/routing/inspector.rb +8 -7
  95. data/lib/action_dispatch/routing/mapper.rb +102 -71
  96. data/lib/action_dispatch/routing/polymorphic_routes.rb +12 -11
  97. data/lib/action_dispatch/routing/redirection.rb +4 -4
  98. data/lib/action_dispatch/routing/route_set.rb +49 -41
  99. data/lib/action_dispatch/routing/url_for.rb +1 -0
  100. data/lib/action_dispatch/system_test_case.rb +29 -24
  101. data/lib/action_dispatch/system_testing/browser.rb +33 -27
  102. data/lib/action_dispatch/system_testing/driver.rb +6 -7
  103. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +47 -6
  104. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +4 -7
  105. data/lib/action_dispatch/testing/assertions.rb +1 -1
  106. data/lib/action_dispatch/testing/assertions/response.rb +2 -4
  107. data/lib/action_dispatch/testing/assertions/routing.rb +5 -5
  108. data/lib/action_dispatch/testing/integration.rb +40 -29
  109. data/lib/action_dispatch/testing/test_process.rb +29 -4
  110. data/lib/action_dispatch/testing/test_request.rb +3 -3
  111. data/lib/action_pack.rb +1 -1
  112. data/lib/action_pack/gem_version.rb +2 -2
  113. metadata +16 -17
  114. data/lib/action_controller/metal/force_ssl.rb +0 -58
  115. data/lib/action_dispatch/http/parameter_filter.rb +0 -12
  116. data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
  117. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
  118. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -119
@@ -136,11 +136,11 @@ module ActionController
136
136
  attr_accessor :ignore_disconnect
137
137
 
138
138
  def initialize(response)
139
+ super(response, SizedQueue.new(10))
139
140
  @error_callback = lambda { true }
140
141
  @cv = new_cond
141
142
  @aborted = false
142
143
  @ignore_disconnect = false
143
- super(response, SizedQueue.new(10))
144
144
  end
145
145
 
146
146
  def write(string)
@@ -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
@@ -85,7 +85,7 @@ module ActionController
85
85
  # * <tt>:fallback_location</tt> - The default fallback location that will be used on missing +Referer+ header.
86
86
  # * <tt>:allow_other_host</tt> - Allow or disallow redirection to the host that is different to the current host, defaults to true.
87
87
  #
88
- # All other options that can be passed to <tt>redirect_to</tt> are accepted as
88
+ # All other options that can be passed to #redirect_to are accepted as
89
89
  # options and the behavior is identical.
90
90
  def redirect_back(fallback_location:, allow_other_host: true, **args)
91
91
  referer = request.headers["Referer"]
@@ -77,6 +77,12 @@ module ActionController
77
77
  end
78
78
  end
79
79
 
80
+ def _set_vary_header
81
+ if self.headers["Vary"].blank? && request.should_apply_vary_header?
82
+ self.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
@@ -32,29 +32,21 @@ module ActionController #:nodoc:
32
32
  # response may be extracted. To prevent this, only XmlHttpRequest (known as XHR or
33
33
  # Ajax) requests are allowed to make requests for JavaScript responses.
34
34
  #
35
- # It's important to remember that XML or JSON requests are also checked by default. If
36
- # you're building an API or an SPA you could change forgery protection method in
37
- # <tt>ApplicationController</tt> (by default: <tt>:exception</tt>):
35
+ # Subclasses of <tt>ActionController::Base</tt> are protected by default with the
36
+ # <tt>:exception</tt> strategy, which raises an
37
+ # <tt>ActionController::InvalidAuthenticityToken</tt> error on unverified requests.
38
+ #
39
+ # APIs may want to disable this behavior since they are typically designed to be
40
+ # state-less: that is, the request API client handles the session instead of Rails.
41
+ # One way to achieve this is to use the <tt>:null_session</tt> strategy instead,
42
+ # which allows unverified requests to be handled, but with an empty session:
38
43
  #
39
44
  # class ApplicationController < ActionController::Base
40
- # protect_from_forgery unless: -> { request.format.json? }
45
+ # protect_from_forgery with: :null_session
41
46
  # end
42
47
  #
43
- # It is generally safe to exclude XHR requests from CSRF protection
44
- # (like the code snippet above does), because XHR requests can only be made from
45
- # the same origin. Note however that any cross-origin third party domain
46
- # allowed via {CORS}[https://en.wikipedia.org/wiki/Cross-origin_resource_sharing]
47
- # will also be able to create XHR requests. Be sure to check your
48
- # CORS configuration before disabling forgery protection for XHR.
49
- #
50
- # CSRF protection is turned on with the <tt>protect_from_forgery</tt> method.
51
- # By default <tt>protect_from_forgery</tt> protects your session with
52
- # <tt>:null_session</tt> method, which provides an empty session
53
- # during request.
54
- #
55
- # We may want to disable CSRF protection for APIs since they are typically
56
- # designed to be state-less. That is, the request API client will handle
57
- # the session for you instead of Rails.
48
+ # Note that API only applications don't include this module or a session middleware
49
+ # by default, and so don't require CSRF protection to be configured.
58
50
  #
59
51
  # The token parameter is named <tt>authenticity_token</tt> by default. The name and
60
52
  # value of this token must be added to every layout that renders forms by including
@@ -98,6 +90,10 @@ module ActionController #:nodoc:
98
90
  config_accessor :default_protect_from_forgery
99
91
  self.default_protect_from_forgery = false
100
92
 
93
+ # Controls whether URL-safe CSRF tokens are generated.
94
+ config_accessor :urlsafe_csrf_tokens, instance_writer: false
95
+ self.urlsafe_csrf_tokens = false
96
+
101
97
  helper_method :form_authenticity_token
102
98
  helper_method :protect_against_forgery?
103
99
  end
@@ -337,7 +333,7 @@ module ActionController #:nodoc:
337
333
  end
338
334
 
339
335
  begin
340
- masked_token = Base64.strict_decode64(encoded_masked_token)
336
+ masked_token = decode_csrf_token(encoded_masked_token)
341
337
  rescue ArgumentError # encoded_masked_token is invalid Base64
342
338
  return false
343
339
  end
@@ -375,7 +371,7 @@ module ActionController #:nodoc:
375
371
  one_time_pad = SecureRandom.random_bytes(AUTHENTICITY_TOKEN_LENGTH)
376
372
  encrypted_csrf_token = xor_byte_strings(one_time_pad, raw_token)
377
373
  masked_token = one_time_pad + encrypted_csrf_token
378
- Base64.strict_encode64(masked_token)
374
+ encode_csrf_token(masked_token)
379
375
  end
380
376
 
381
377
  def compare_with_real_token(token, session) # :doc:
@@ -390,7 +386,7 @@ module ActionController #:nodoc:
390
386
  if per_form_csrf_tokens
391
387
  correct_token = per_form_csrf_token(
392
388
  session,
393
- normalize_action_path(request.fullpath),
389
+ request.path.chomp("/"),
394
390
  request.request_method
395
391
  )
396
392
 
@@ -401,8 +397,8 @@ module ActionController #:nodoc:
401
397
  end
402
398
 
403
399
  def real_csrf_token(session) # :doc:
404
- session[:_csrf_token] ||= SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
405
- Base64.strict_decode64(session[:_csrf_token])
400
+ session[:_csrf_token] ||= generate_csrf_token
401
+ decode_csrf_token(session[:_csrf_token])
406
402
  end
407
403
 
408
404
  def per_form_csrf_token(session, action_path, method) # :doc:
@@ -470,5 +466,33 @@ module ActionController #:nodoc:
470
466
  uri = URI.parse(action_path)
471
467
  uri.path.chomp("/")
472
468
  end
469
+
470
+ def generate_csrf_token # :nodoc:
471
+ if urlsafe_csrf_tokens
472
+ SecureRandom.urlsafe_base64(AUTHENTICITY_TOKEN_LENGTH, padding: false)
473
+ else
474
+ SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
475
+ end
476
+ end
477
+
478
+ def encode_csrf_token(csrf_token) # :nodoc:
479
+ if urlsafe_csrf_tokens
480
+ Base64.urlsafe_encode64(csrf_token, padding: false)
481
+ else
482
+ Base64.strict_encode64(csrf_token)
483
+ end
484
+ end
485
+
486
+ def decode_csrf_token(encoded_csrf_token) # :nodoc:
487
+ if urlsafe_csrf_tokens
488
+ Base64.urlsafe_decode64(encoded_csrf_token)
489
+ else
490
+ begin
491
+ Base64.strict_decode64(encoded_csrf_token)
492
+ rescue ArgumentError
493
+ Base64.urlsafe_decode64(encoded_csrf_token)
494
+ end
495
+ end
496
+ end
473
497
  end
474
498
  end
@@ -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?
@@ -19,12 +19,36 @@ module ActionController
19
19
  # params.require(:a)
20
20
  # # => ActionController::ParameterMissing: param is missing or the value is empty: a
21
21
  class ParameterMissing < KeyError
22
- attr_reader :param # :nodoc:
22
+ attr_reader :param, :keys # :nodoc:
23
23
 
24
- def initialize(param) # :nodoc:
24
+ def initialize(param, keys = nil) # :nodoc:
25
25
  @param = param
26
+ @keys = keys
26
27
  super("param is missing or the value is empty: #{param}")
27
28
  end
29
+
30
+ class Correction
31
+ def initialize(error)
32
+ @error = error
33
+ end
34
+
35
+ def corrections
36
+ if @error.param && @error.keys
37
+ maybe_these = @error.keys
38
+
39
+ maybe_these.sort_by { |n|
40
+ DidYouMean::Jaro.distance(@error.param.to_s, n)
41
+ }.reverse.first(4)
42
+ else
43
+ []
44
+ end
45
+ end
46
+ end
47
+
48
+ # We may not have DYM, and DYM might not let us register error handlers
49
+ if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
50
+ DidYouMean.correct_error(self, Correction)
51
+ end
28
52
  end
29
53
 
30
54
  # Raised when a supplied parameter is not expected and
@@ -180,6 +204,14 @@ module ActionController
180
204
  #
181
205
  # Returns true if the given key is present in the parameters.
182
206
 
207
+ ##
208
+ # :method: member?
209
+ #
210
+ # :call-seq:
211
+ # member?(key)
212
+ #
213
+ # Returns true if the given key is present in the parameters.
214
+
183
215
  ##
184
216
  # :method: keys
185
217
  #
@@ -211,7 +243,7 @@ module ActionController
211
243
  # values()
212
244
  #
213
245
  # Returns a new array of the values of the parameters.
214
- delegate :keys, :key?, :has_key?, :values, :has_value?, :value?, :empty?, :include?,
246
+ delegate :keys, :key?, :has_key?, :member?, :values, :has_value?, :value?, :empty?, :include?,
215
247
  :as_json, :to_s, :each_key, to: :@parameters
216
248
 
217
249
  # By default, never raise an UnpermittedParameters exception if these
@@ -220,9 +252,15 @@ module ActionController
220
252
  # to change these is to specify `always_permitted_parameters` in your
221
253
  # config. For instance:
222
254
  #
223
- # config.always_permitted_parameters = %w( controller action format )
255
+ # config.action_controller.always_permitted_parameters = %w( controller action format )
224
256
  cattr_accessor :always_permitted_parameters, default: %w( controller action )
225
257
 
258
+ class << self
259
+ def nested_attribute?(key, value) # :nodoc:
260
+ /\A-?\d+\z/.match?(key) && (value.is_a?(Hash) || value.is_a?(Parameters))
261
+ end
262
+ end
263
+
226
264
  # Returns a new instance of <tt>ActionController::Parameters</tt>.
227
265
  # Also, sets the +permitted+ attribute to the default value of
228
266
  # <tt>ActionController::Parameters.permit_all_parameters</tt>.
@@ -253,6 +291,11 @@ module ActionController
253
291
  @parameters == other
254
292
  end
255
293
  end
294
+ alias eql? ==
295
+
296
+ def hash
297
+ [@parameters.hash, @permitted].hash
298
+ end
256
299
 
257
300
  # Returns a safe <tt>ActiveSupport::HashWithIndifferentAccess</tt>
258
301
  # representation of the parameters with all unpermitted keys removed.
@@ -341,6 +384,7 @@ module ActionController
341
384
  # Convert all hashes in values into parameters, then yield each pair in
342
385
  # the same way as <tt>Hash#each_pair</tt>.
343
386
  def each_pair(&block)
387
+ return to_enum(__callee__) unless block_given?
344
388
  @parameters.each_pair do |key, value|
345
389
  yield [key, convert_hashes_to_parameters(key, value)]
346
390
  end
@@ -352,6 +396,7 @@ module ActionController
352
396
  # Convert all hashes in values into parameters, then yield each value in
353
397
  # the same way as <tt>Hash#each_value</tt>.
354
398
  def each_value(&block)
399
+ return to_enum(:each_value) unless block_given?
355
400
  @parameters.each_pair do |key, value|
356
401
  yield convert_hashes_to_parameters(key, value)
357
402
  end
@@ -459,7 +504,7 @@ module ActionController
459
504
  if value.present? || value == false
460
505
  value
461
506
  else
462
- raise ParameterMissing.new(key)
507
+ raise ParameterMissing.new(key, @parameters.keys)
463
508
  end
464
509
  end
465
510
 
@@ -596,7 +641,7 @@ module ActionController
596
641
  if block_given?
597
642
  yield
598
643
  else
599
- args.fetch(0) { raise ActionController::ParameterMissing.new(key) }
644
+ args.fetch(0) { raise ActionController::ParameterMissing.new(key, @parameters.keys) }
600
645
  end
601
646
  }
602
647
  )
@@ -691,6 +736,23 @@ module ActionController
691
736
  self
692
737
  end
693
738
 
739
+ # Returns a new <tt>ActionController::Parameters</tt> instance with the
740
+ # results of running +block+ once for every key. This includes the keys
741
+ # from the root hash and from all nested hashes and arrays. The values are unchanged.
742
+ def deep_transform_keys(&block)
743
+ new_instance_with_inherited_permitted_status(
744
+ @parameters.deep_transform_keys(&block)
745
+ )
746
+ end
747
+
748
+ # Returns the <tt>ActionController::Parameters</tt> instance changing its keys.
749
+ # This includes the keys from the root hash and from all nested hashes and arrays.
750
+ # The values are unchanged.
751
+ def deep_transform_keys!(&block)
752
+ @parameters.deep_transform_keys!(&block)
753
+ self
754
+ end
755
+
694
756
  # Deletes a key-value pair from +Parameters+ and returns the value. If
695
757
  # +key+ is not found, returns +nil+ (or, with optional code block, yields
696
758
  # +key+ and returns the result). Cf. +#extract!+, which returns the
@@ -725,6 +787,28 @@ module ActionController
725
787
  end
726
788
  alias_method :delete_if, :reject!
727
789
 
790
+ # Returns a new instance of <tt>ActionController::Parameters</tt> with +nil+ values removed.
791
+ def compact
792
+ new_instance_with_inherited_permitted_status(@parameters.compact)
793
+ end
794
+
795
+ # Removes all +nil+ values in place and returns +self+, or +nil+ if no changes were made.
796
+ def compact!
797
+ self if @parameters.compact!
798
+ end
799
+
800
+ # Returns a new instance of <tt>ActionController::Parameters</tt> without the blank values.
801
+ # Uses Object#blank? for determining if a value is blank.
802
+ def compact_blank
803
+ reject { |_k, v| v.blank? }
804
+ end
805
+
806
+ # Removes all blank values in place and returns self.
807
+ # Uses Object#blank? for determining if a value is blank.
808
+ def compact_blank!
809
+ reject! { |_k, v| v.blank? }
810
+ end
811
+
728
812
  # Returns values that were assigned to the given +keys+. Note that all the
729
813
  # +Hash+ objects will be converted to <tt>ActionController::Parameters</tt>.
730
814
  def values_at(*keys)
@@ -771,7 +855,7 @@ module ActionController
771
855
  end
772
856
 
773
857
  def inspect
774
- "<#{self.class} #{@parameters} permitted: #{@permitted}>"
858
+ "#<#{self.class} #{@parameters} permitted: #{@permitted}>"
775
859
  end
776
860
 
777
861
  def self.hook_into_yaml_loading # :nodoc:
@@ -813,8 +897,14 @@ module ActionController
813
897
 
814
898
  attr_writer :permitted
815
899
 
816
- def fields_for_style?
817
- @parameters.all? { |k, v| k =~ /\A-?\d+\z/ && (v.is_a?(Hash) || v.is_a?(Parameters)) }
900
+ def nested_attributes?
901
+ @parameters.any? { |k, v| Parameters.nested_attribute?(k, v) }
902
+ end
903
+
904
+ def each_nested_attribute
905
+ hash = self.class.new
906
+ self.each { |k, v| hash[k] = yield v if Parameters.nested_attribute?(k, v) }
907
+ hash
818
908
  end
819
909
 
820
910
  private
@@ -859,15 +949,13 @@ module ActionController
859
949
  end
860
950
  end
861
951
 
862
- def each_element(object)
952
+ def each_element(object, &block)
863
953
  case object
864
954
  when Array
865
955
  object.grep(Parameters).map { |el| yield el }.compact
866
956
  when Parameters
867
- if object.fields_for_style?
868
- hash = object.class.new
869
- object.each { |k, v| hash[k] = yield v }
870
- hash
957
+ if object.nested_attributes?
958
+ object.each_nested_attribute(&block)
871
959
  else
872
960
  yield object
873
961
  end
@@ -895,7 +983,7 @@ module ActionController
895
983
  # --- Filtering ----------------------------------------------------------
896
984
  #
897
985
 
898
- # This is a white list of permitted scalar types that includes the ones
986
+ # This is a list of permitted scalar types that includes the ones
899
987
  # supported in XML and JSON requests.
900
988
  #
901
989
  # This list is in particular used to filter ordinary requests, String goes