actionpack 6.0.4.4 → 6.1.0.rc1

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 (112) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +240 -317
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/abstract_controller/base.rb +35 -2
  6. data/lib/abstract_controller/callbacks.rb +2 -2
  7. data/lib/abstract_controller/helpers.rb +105 -90
  8. data/lib/abstract_controller/rendering.rb +9 -9
  9. data/lib/abstract_controller/translation.rb +8 -2
  10. data/lib/abstract_controller.rb +1 -0
  11. data/lib/action_controller/api.rb +2 -2
  12. data/lib/action_controller/base.rb +4 -2
  13. data/lib/action_controller/caching.rb +0 -1
  14. data/lib/action_controller/log_subscriber.rb +3 -3
  15. data/lib/action_controller/metal/conditional_get.rb +10 -2
  16. data/lib/action_controller/metal/content_security_policy.rb +1 -1
  17. data/lib/action_controller/metal/data_streaming.rb +1 -1
  18. data/lib/action_controller/metal/etag_with_template_digest.rb +2 -4
  19. data/lib/action_controller/metal/exceptions.rb +33 -0
  20. data/lib/action_controller/metal/feature_policy.rb +46 -0
  21. data/lib/action_controller/metal/head.rb +7 -4
  22. data/lib/action_controller/metal/helpers.rb +11 -1
  23. data/lib/action_controller/metal/http_authentication.rb +5 -3
  24. data/lib/action_controller/metal/implicit_render.rb +1 -1
  25. data/lib/action_controller/metal/instrumentation.rb +11 -9
  26. data/lib/action_controller/metal/live.rb +1 -1
  27. data/lib/action_controller/metal/logging.rb +20 -0
  28. data/lib/action_controller/metal/mime_responds.rb +6 -2
  29. data/lib/action_controller/metal/parameter_encoding.rb +35 -4
  30. data/lib/action_controller/metal/params_wrapper.rb +16 -11
  31. data/lib/action_controller/metal/redirecting.rb +1 -1
  32. data/lib/action_controller/metal/rendering.rb +6 -0
  33. data/lib/action_controller/metal/request_forgery_protection.rb +1 -1
  34. data/lib/action_controller/metal/rescue.rb +1 -1
  35. data/lib/action_controller/metal/strong_parameters.rb +103 -15
  36. data/lib/action_controller/metal.rb +2 -2
  37. data/lib/action_controller/renderer.rb +24 -13
  38. data/lib/action_controller/test_case.rb +62 -56
  39. data/lib/action_controller.rb +2 -3
  40. data/lib/action_dispatch/http/cache.rb +12 -10
  41. data/lib/action_dispatch/http/content_security_policy.rb +5 -1
  42. data/lib/action_dispatch/http/feature_policy.rb +168 -0
  43. data/lib/action_dispatch/http/filter_parameters.rb +1 -1
  44. data/lib/action_dispatch/http/filter_redirect.rb +1 -1
  45. data/lib/action_dispatch/http/headers.rb +3 -2
  46. data/lib/action_dispatch/http/mime_negotiation.rb +14 -8
  47. data/lib/action_dispatch/http/mime_type.rb +29 -16
  48. data/lib/action_dispatch/http/parameters.rb +1 -19
  49. data/lib/action_dispatch/http/request.rb +24 -8
  50. data/lib/action_dispatch/http/response.rb +17 -16
  51. data/lib/action_dispatch/http/url.rb +3 -2
  52. data/lib/action_dispatch/journey/formatter.rb +53 -28
  53. data/lib/action_dispatch/journey/gtg/builder.rb +22 -36
  54. data/lib/action_dispatch/journey/gtg/simulator.rb +8 -7
  55. data/lib/action_dispatch/journey/gtg/transition_table.rb +6 -4
  56. data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
  57. data/lib/action_dispatch/journey/nodes/node.rb +4 -3
  58. data/lib/action_dispatch/journey/parser.rb +13 -13
  59. data/lib/action_dispatch/journey/parser.y +1 -1
  60. data/lib/action_dispatch/journey/path/pattern.rb +13 -18
  61. data/lib/action_dispatch/journey/route.rb +7 -18
  62. data/lib/action_dispatch/journey/router/utils.rb +6 -4
  63. data/lib/action_dispatch/journey/router.rb +26 -30
  64. data/lib/action_dispatch/journey.rb +0 -2
  65. data/lib/action_dispatch/middleware/actionable_exceptions.rb +1 -1
  66. data/lib/action_dispatch/middleware/cookies.rb +67 -32
  67. data/lib/action_dispatch/middleware/debug_exceptions.rb +8 -15
  68. data/lib/action_dispatch/middleware/debug_view.rb +1 -1
  69. data/lib/action_dispatch/middleware/exception_wrapper.rb +28 -16
  70. data/lib/action_dispatch/middleware/host_authorization.rb +35 -35
  71. data/lib/action_dispatch/middleware/remote_ip.rb +5 -4
  72. data/lib/action_dispatch/middleware/request_id.rb +4 -5
  73. data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -2
  74. data/lib/action_dispatch/middleware/session/cookie_store.rb +2 -2
  75. data/lib/action_dispatch/middleware/ssl.rb +9 -6
  76. data/lib/action_dispatch/middleware/stack.rb +18 -0
  77. data/lib/action_dispatch/middleware/static.rb +154 -93
  78. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +18 -0
  79. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +2 -5
  80. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +2 -2
  81. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +2 -3
  82. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +88 -8
  83. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  84. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +12 -1
  85. data/lib/action_dispatch/railtie.rb +3 -2
  86. data/lib/action_dispatch/request/session.rb +2 -8
  87. data/lib/action_dispatch/request/utils.rb +26 -2
  88. data/lib/action_dispatch/routing/inspector.rb +8 -7
  89. data/lib/action_dispatch/routing/mapper.rb +102 -71
  90. data/lib/action_dispatch/routing/polymorphic_routes.rb +16 -19
  91. data/lib/action_dispatch/routing/redirection.rb +3 -3
  92. data/lib/action_dispatch/routing/route_set.rb +49 -41
  93. data/lib/action_dispatch/system_test_case.rb +29 -24
  94. data/lib/action_dispatch/system_testing/browser.rb +33 -27
  95. data/lib/action_dispatch/system_testing/driver.rb +6 -7
  96. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +47 -6
  97. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +4 -7
  98. data/lib/action_dispatch/testing/assertions/response.rb +2 -4
  99. data/lib/action_dispatch/testing/assertions/routing.rb +5 -5
  100. data/lib/action_dispatch/testing/assertions.rb +1 -1
  101. data/lib/action_dispatch/testing/integration.rb +38 -27
  102. data/lib/action_dispatch/testing/test_process.rb +29 -4
  103. data/lib/action_dispatch/testing/test_request.rb +3 -3
  104. data/lib/action_dispatch.rb +3 -2
  105. data/lib/action_pack/gem_version.rb +3 -3
  106. data/lib/action_pack.rb +1 -1
  107. metadata +20 -21
  108. data/lib/action_controller/metal/force_ssl.rb +0 -58
  109. data/lib/action_dispatch/http/parameter_filter.rb +0 -12
  110. data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
  111. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
  112. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -119
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionController #:nodoc:
4
+ # HTTP Feature Policy is a web standard for defining a mechanism to
5
+ # allow and deny the use of browser features in its own context, and
6
+ # in content within any <iframe> elements in the document.
7
+ #
8
+ # Full details of HTTP Feature 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.feature_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
+ # feature_policy do |p|
28
+ # p.geolocation "https://example.com"
29
+ # end
30
+ # end
31
+ module FeaturePolicy
32
+ extend ActiveSupport::Concern
33
+
34
+ module ClassMethods
35
+ def feature_policy(**options, &block)
36
+ before_action(options) do
37
+ if block_given?
38
+ policy = request.feature_policy.clone
39
+ yield policy
40
+ request.feature_policy = policy
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -29,19 +29,22 @@ module ActionController
29
29
  content_type = options.delete(:content_type)
30
30
 
31
31
  options.each do |key, value|
32
- headers[key.to_s.dasherize.split("-").each { |v| v[0] = v[0].chr.upcase }.join("-")] = value.to_s
32
+ headers[key.to_s.split(/[-_]/).each { |v| v[0] = v[0].upcase }.join("-")] = value.to_s
33
33
  end
34
34
 
35
35
  self.status = status
36
36
  self.location = url_for(location) if location
37
37
 
38
- self.response_body = ""
39
-
40
38
  if include_content?(response_code)
41
- self.content_type = content_type || (Mime[formats.first] if formats) || Mime[:html]
39
+ unless self.media_type
40
+ self.content_type = content_type || (Mime[formats.first] if formats) || Mime[:html]
41
+ end
42
+
42
43
  response.charset = false
43
44
  end
44
45
 
46
+ self.response_body = ""
47
+
45
48
  true
46
49
  end
47
50
 
@@ -11,7 +11,12 @@ module ActionController
11
11
  #
12
12
  # In previous versions of \Rails the controller will include a helper which
13
13
  # matches the name of the controller, e.g., <tt>MyController</tt> will automatically
14
- # include <tt>MyHelper</tt>. To return old behavior set +config.action_controller.include_all_helpers+ to +false+.
14
+ # include <tt>MyHelper</tt>. You can revert to the old behavior with the following:
15
+ #
16
+ # # config/application.rb
17
+ # class Application < Rails::Application
18
+ # config.action_controller.include_all_helpers = false
19
+ # end
15
20
  #
16
21
  # Additional helpers can be specified using the +helper+ class method in ActionController::Base or any
17
22
  # controller which inherits from it.
@@ -73,6 +78,11 @@ module ActionController
73
78
  end
74
79
 
75
80
  # Provides a proxy to access helper methods from outside the view.
81
+ #
82
+ # Note that the proxy is rendered under a different view context.
83
+ # This may cause incorrect behaviour with capture methods. Consider
84
+ # using {helper}[rdoc-ref:AbstractController::Helpers::ClassMethods#helper]
85
+ # instead when using +capture+.
76
86
  def helpers
77
87
  @helper_proxy ||= begin
78
88
  proxy = ActionView::Base.empty
@@ -76,6 +76,8 @@ module ActionController
76
76
 
77
77
  def http_basic_authenticate_or_request_with(name:, password:, realm: nil, message: nil)
78
78
  authenticate_or_request_with_http_basic(realm, message) do |given_name, given_password|
79
+ # This comparison uses & so that it doesn't short circuit and
80
+ # uses `secure_compare` so that length information isn't leaked.
79
81
  ActiveSupport::SecurityUtils.secure_compare(given_name, name) &
80
82
  ActiveSupport::SecurityUtils.secure_compare(given_password, password)
81
83
  end
@@ -136,7 +138,7 @@ module ActionController
136
138
  #
137
139
  # === Simple \Digest example
138
140
  #
139
- # require 'digest/md5'
141
+ # require "digest/md5"
140
142
  # class PostsController < ApplicationController
141
143
  # REALM = "SuperSecret"
142
144
  # USERS = {"dhh" => "secret", #plain text password
@@ -405,7 +407,7 @@ module ActionController
405
407
  module Token
406
408
  TOKEN_KEY = "token="
407
409
  TOKEN_REGEX = /^(Token|Bearer)\s+/
408
- AUTHN_PAIR_DELIMITERS = /(?:,|;|\t)/
410
+ AUTHN_PAIR_DELIMITERS = /(?:,|;|\t+)/
409
411
  extend self
410
412
 
411
413
  module ControllerMethods
@@ -482,7 +484,7 @@ module ActionController
482
484
  def raw_params(auth)
483
485
  _raw_params = auth.sub(TOKEN_REGEX, "").split(/\s*#{AUTHN_PAIR_DELIMITERS}\s*/)
484
486
 
485
- if !(_raw_params.first =~ %r{\A#{TOKEN_KEY}})
487
+ if !_raw_params.first.start_with?(TOKEN_KEY)
486
488
  _raw_params[0] = "#{TOKEN_KEY}#{_raw_params.first}"
487
489
  end
488
490
 
@@ -22,7 +22,7 @@ module ActionController
22
22
  # Third, if we DON'T find a template AND the request is a page load in a web
23
23
  # browser (technically, a non-XHR GET request for an HTML response) where
24
24
  # you reasonably expect to have rendered a template, then we raise
25
- # <tt>ActionView::UnknownFormat</tt> with an explanation.
25
+ # <tt>ActionController::MissingExactTemplate</tt> with an explanation.
26
26
  #
27
27
  # Finally, if we DON'T find a template AND the request isn't a browser page
28
28
  # load, then we implicitly respond with <tt>204 No Content</tt>.
@@ -16,10 +16,11 @@ module ActionController
16
16
 
17
17
  attr_internal :view_runtime
18
18
 
19
- def process_action(*args)
19
+ def process_action(*)
20
20
  raw_payload = {
21
21
  controller: self.class.name,
22
22
  action: action_name,
23
+ request: request,
23
24
  params: request.filtered_parameters,
24
25
  headers: request.headers,
25
26
  format: request.format.ref,
@@ -27,18 +28,19 @@ module ActionController
27
28
  path: request.fullpath
28
29
  }
29
30
 
30
- ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup)
31
+ ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload)
31
32
 
32
33
  ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
33
- super.tap do
34
- payload[:status] = response.status
35
- end
34
+ result = super
35
+ payload[:response] = response
36
+ payload[:status] = response.status
37
+ result
36
38
  ensure
37
39
  append_info_to_payload(payload)
38
40
  end
39
41
  end
40
42
 
41
- def render(*args)
43
+ def render(*)
42
44
  render_output = nil
43
45
  self.view_runtime = cleanup_view_runtime do
44
46
  Benchmark.ms { render_output = super }
@@ -59,8 +61,8 @@ module ActionController
59
61
  end
60
62
  end
61
63
 
62
- def redirect_to(*args)
63
- ActiveSupport::Notifications.instrument("redirect_to.action_controller") do |payload|
64
+ def redirect_to(*)
65
+ ActiveSupport::Notifications.instrument("redirect_to.action_controller", request: request) do |payload|
64
66
  result = super
65
67
  payload[:status] = response.status
66
68
  payload[:location] = response.filtered_location
@@ -70,7 +72,7 @@ module ActionController
70
72
 
71
73
  private
72
74
  # A hook invoked every time a before callback is halted.
73
- def halted_callback_hook(filter)
75
+ def halted_callback_hook(filter, _)
74
76
  ActiveSupport::Notifications.instrument("halted_callback.action_controller", filter: filter)
75
77
  end
76
78
 
@@ -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
 
@@ -275,10 +281,7 @@ module ActionController
275
281
  return false unless request.has_content_type?
276
282
 
277
283
  ref = request.content_mime_type.ref
278
-
279
284
  _wrapper_formats.include?(ref) && _wrapper_key && !request.parameters.key?(_wrapper_key)
280
- rescue ActionDispatch::Http::Parameters::ParseError
281
- false
282
285
  end
283
286
 
284
287
  def _perform_parameter_wrapping
@@ -292,6 +295,8 @@ module ActionController
292
295
 
293
296
  # This will display the wrapped hash in the log file.
294
297
  request.filtered_parameters.merge! wrapped_filtered_hash
298
+ rescue ActionDispatch::Http::Parameters::ParseError
299
+ # swallow parse error exception
295
300
  end
296
301
  end
297
302
  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
@@ -386,7 +386,7 @@ module ActionController #:nodoc:
386
386
  if per_form_csrf_tokens
387
387
  correct_token = per_form_csrf_token(
388
388
  session,
389
- normalize_action_path(request.fullpath),
389
+ request.path.chomp("/"),
390
390
  request.request_method
391
391
  )
392
392
 
@@ -18,7 +18,7 @@ module ActionController #:nodoc:
18
18
  end
19
19
 
20
20
  private
21
- def process_action(*args)
21
+ def process_action(*)
22
22
  super
23
23
  rescue Exception => exception
24
24
  request.env["action_dispatch.show_detailed_exceptions"] ||= show_detailed_exceptions?
@@ -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