actionpack 6.0.3 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +246 -217
- data/MIT-LICENSE +1 -1
- data/lib/abstract_controller.rb +1 -0
- data/lib/abstract_controller/base.rb +35 -2
- data/lib/abstract_controller/callbacks.rb +2 -2
- data/lib/abstract_controller/helpers.rb +105 -90
- data/lib/abstract_controller/rendering.rb +9 -9
- data/lib/abstract_controller/translation.rb +8 -2
- data/lib/action_controller.rb +2 -3
- data/lib/action_controller/api.rb +2 -2
- data/lib/action_controller/base.rb +4 -2
- data/lib/action_controller/caching.rb +0 -1
- data/lib/action_controller/log_subscriber.rb +3 -3
- data/lib/action_controller/metal.rb +2 -2
- data/lib/action_controller/metal/conditional_get.rb +10 -2
- data/lib/action_controller/metal/content_security_policy.rb +1 -1
- data/lib/action_controller/metal/data_streaming.rb +1 -1
- data/lib/action_controller/metal/etag_with_template_digest.rb +2 -4
- data/lib/action_controller/metal/exceptions.rb +33 -0
- data/lib/action_controller/metal/feature_policy.rb +46 -0
- data/lib/action_controller/metal/head.rb +7 -4
- data/lib/action_controller/metal/helpers.rb +11 -1
- data/lib/action_controller/metal/http_authentication.rb +4 -2
- data/lib/action_controller/metal/implicit_render.rb +1 -1
- data/lib/action_controller/metal/instrumentation.rb +11 -9
- data/lib/action_controller/metal/live.rb +1 -1
- data/lib/action_controller/metal/logging.rb +20 -0
- data/lib/action_controller/metal/mime_responds.rb +6 -2
- data/lib/action_controller/metal/parameter_encoding.rb +35 -4
- data/lib/action_controller/metal/params_wrapper.rb +14 -8
- data/lib/action_controller/metal/redirecting.rb +1 -1
- data/lib/action_controller/metal/rendering.rb +6 -0
- data/lib/action_controller/metal/request_forgery_protection.rb +74 -30
- data/lib/action_controller/metal/rescue.rb +1 -1
- data/lib/action_controller/metal/strong_parameters.rb +107 -15
- data/lib/action_controller/renderer.rb +24 -13
- data/lib/action_controller/test_case.rb +62 -56
- data/lib/action_dispatch.rb +3 -2
- data/lib/action_dispatch/http/cache.rb +12 -10
- data/lib/action_dispatch/http/content_disposition.rb +2 -2
- data/lib/action_dispatch/http/content_security_policy.rb +5 -1
- data/lib/action_dispatch/http/feature_policy.rb +168 -0
- data/lib/action_dispatch/http/filter_parameters.rb +1 -1
- data/lib/action_dispatch/http/filter_redirect.rb +1 -1
- data/lib/action_dispatch/http/headers.rb +3 -2
- data/lib/action_dispatch/http/mime_negotiation.rb +20 -8
- data/lib/action_dispatch/http/mime_type.rb +28 -15
- data/lib/action_dispatch/http/parameters.rb +1 -19
- data/lib/action_dispatch/http/request.rb +26 -8
- data/lib/action_dispatch/http/response.rb +17 -16
- data/lib/action_dispatch/http/url.rb +3 -2
- data/lib/action_dispatch/journey.rb +0 -2
- data/lib/action_dispatch/journey/formatter.rb +53 -28
- data/lib/action_dispatch/journey/gtg/builder.rb +22 -36
- data/lib/action_dispatch/journey/gtg/simulator.rb +8 -7
- data/lib/action_dispatch/journey/gtg/transition_table.rb +6 -4
- data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
- data/lib/action_dispatch/journey/nodes/node.rb +4 -3
- data/lib/action_dispatch/journey/parser.rb +13 -13
- data/lib/action_dispatch/journey/parser.y +1 -1
- data/lib/action_dispatch/journey/path/pattern.rb +13 -18
- data/lib/action_dispatch/journey/route.rb +7 -18
- data/lib/action_dispatch/journey/router.rb +26 -30
- data/lib/action_dispatch/journey/router/utils.rb +6 -4
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +9 -2
- data/lib/action_dispatch/middleware/cookies.rb +74 -33
- data/lib/action_dispatch/middleware/debug_exceptions.rb +10 -17
- data/lib/action_dispatch/middleware/debug_view.rb +1 -1
- data/lib/action_dispatch/middleware/exception_wrapper.rb +29 -17
- data/lib/action_dispatch/middleware/host_authorization.rb +23 -3
- data/lib/action_dispatch/middleware/public_exceptions.rb +1 -1
- data/lib/action_dispatch/middleware/remote_ip.rb +5 -4
- data/lib/action_dispatch/middleware/request_id.rb +4 -5
- data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -2
- data/lib/action_dispatch/middleware/session/cookie_store.rb +2 -2
- data/lib/action_dispatch/middleware/ssl.rb +9 -6
- data/lib/action_dispatch/middleware/stack.rb +18 -0
- data/lib/action_dispatch/middleware/static.rb +154 -93
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +18 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +2 -5
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +88 -8
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +12 -1
- data/lib/action_dispatch/railtie.rb +3 -2
- data/lib/action_dispatch/request/session.rb +2 -8
- data/lib/action_dispatch/request/utils.rb +26 -2
- data/lib/action_dispatch/routing/inspector.rb +8 -7
- data/lib/action_dispatch/routing/mapper.rb +102 -71
- data/lib/action_dispatch/routing/polymorphic_routes.rb +12 -11
- data/lib/action_dispatch/routing/redirection.rb +3 -3
- data/lib/action_dispatch/routing/route_set.rb +49 -41
- data/lib/action_dispatch/routing/url_for.rb +1 -0
- data/lib/action_dispatch/system_test_case.rb +29 -24
- data/lib/action_dispatch/system_testing/browser.rb +33 -27
- data/lib/action_dispatch/system_testing/driver.rb +6 -7
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +47 -6
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +4 -7
- data/lib/action_dispatch/testing/assertions.rb +1 -1
- data/lib/action_dispatch/testing/assertions/response.rb +2 -4
- data/lib/action_dispatch/testing/assertions/routing.rb +5 -5
- data/lib/action_dispatch/testing/integration.rb +38 -27
- data/lib/action_dispatch/testing/test_process.rb +29 -4
- data/lib/action_dispatch/testing/test_request.rb +3 -3
- data/lib/action_pack.rb +1 -1
- data/lib/action_pack/gem_version.rb +3 -3
- metadata +20 -21
- data/lib/action_controller/metal/force_ssl.rb +0 -58
- data/lib/action_dispatch/http/parameter_filter.rb +0 -12
- data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
- data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
- data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -119
@@ -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
|
19
|
-
@_parameter_encodings
|
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] =
|
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
|
112
|
-
|
113
|
-
|
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.
|
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(*
|
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
|
-
|
269
|
-
parameters.except(*(exclude + EXCLUDE_PARAMETERS))
|
275
|
+
parameters.except(*EXCLUDE_PARAMETERS)
|
270
276
|
end
|
271
277
|
end
|
272
278
|
|
@@ -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
|
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
|
-
#
|
36
|
-
#
|
37
|
-
# <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
|
45
|
+
# protect_from_forgery with: :null_session
|
41
46
|
# end
|
42
47
|
#
|
43
|
-
#
|
44
|
-
#
|
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
|
@@ -322,13 +318,10 @@ module ActionController #:nodoc:
|
|
322
318
|
action_path = normalize_action_path(action)
|
323
319
|
per_form_csrf_token(session, action_path, method)
|
324
320
|
else
|
325
|
-
|
321
|
+
global_csrf_token(session)
|
326
322
|
end
|
327
323
|
|
328
|
-
|
329
|
-
encrypted_csrf_token = xor_byte_strings(one_time_pad, raw_token)
|
330
|
-
masked_token = one_time_pad + encrypted_csrf_token
|
331
|
-
Base64.strict_encode64(masked_token)
|
324
|
+
mask_token(raw_token)
|
332
325
|
end
|
333
326
|
|
334
327
|
# Checks the client's masked token to see if it matches the
|
@@ -340,7 +333,7 @@ module ActionController #:nodoc:
|
|
340
333
|
end
|
341
334
|
|
342
335
|
begin
|
343
|
-
masked_token =
|
336
|
+
masked_token = decode_csrf_token(encoded_masked_token)
|
344
337
|
rescue ArgumentError # encoded_masked_token is invalid Base64
|
345
338
|
return false
|
346
339
|
end
|
@@ -358,7 +351,8 @@ module ActionController #:nodoc:
|
|
358
351
|
elsif masked_token.length == AUTHENTICITY_TOKEN_LENGTH * 2
|
359
352
|
csrf_token = unmask_token(masked_token)
|
360
353
|
|
361
|
-
|
354
|
+
compare_with_global_token(csrf_token, session) ||
|
355
|
+
compare_with_real_token(csrf_token, session) ||
|
362
356
|
valid_per_form_csrf_token?(csrf_token, session)
|
363
357
|
else
|
364
358
|
false # Token is malformed.
|
@@ -373,15 +367,26 @@ module ActionController #:nodoc:
|
|
373
367
|
xor_byte_strings(one_time_pad, encrypted_csrf_token)
|
374
368
|
end
|
375
369
|
|
370
|
+
def mask_token(raw_token) # :doc:
|
371
|
+
one_time_pad = SecureRandom.random_bytes(AUTHENTICITY_TOKEN_LENGTH)
|
372
|
+
encrypted_csrf_token = xor_byte_strings(one_time_pad, raw_token)
|
373
|
+
masked_token = one_time_pad + encrypted_csrf_token
|
374
|
+
encode_csrf_token(masked_token)
|
375
|
+
end
|
376
|
+
|
376
377
|
def compare_with_real_token(token, session) # :doc:
|
377
378
|
ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, real_csrf_token(session))
|
378
379
|
end
|
379
380
|
|
381
|
+
def compare_with_global_token(token, session) # :doc:
|
382
|
+
ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, global_csrf_token(session))
|
383
|
+
end
|
384
|
+
|
380
385
|
def valid_per_form_csrf_token?(token, session) # :doc:
|
381
386
|
if per_form_csrf_tokens
|
382
387
|
correct_token = per_form_csrf_token(
|
383
388
|
session,
|
384
|
-
|
389
|
+
request.path.chomp("/"),
|
385
390
|
request.request_method
|
386
391
|
)
|
387
392
|
|
@@ -392,15 +397,26 @@ module ActionController #:nodoc:
|
|
392
397
|
end
|
393
398
|
|
394
399
|
def real_csrf_token(session) # :doc:
|
395
|
-
session[:_csrf_token] ||=
|
396
|
-
|
400
|
+
session[:_csrf_token] ||= generate_csrf_token
|
401
|
+
decode_csrf_token(session[:_csrf_token])
|
397
402
|
end
|
398
403
|
|
399
404
|
def per_form_csrf_token(session, action_path, method) # :doc:
|
405
|
+
csrf_token_hmac(session, [action_path, method.downcase].join("#"))
|
406
|
+
end
|
407
|
+
|
408
|
+
GLOBAL_CSRF_TOKEN_IDENTIFIER = "!real_csrf_token"
|
409
|
+
private_constant :GLOBAL_CSRF_TOKEN_IDENTIFIER
|
410
|
+
|
411
|
+
def global_csrf_token(session) # :doc:
|
412
|
+
csrf_token_hmac(session, GLOBAL_CSRF_TOKEN_IDENTIFIER)
|
413
|
+
end
|
414
|
+
|
415
|
+
def csrf_token_hmac(session, identifier) # :doc:
|
400
416
|
OpenSSL::HMAC.digest(
|
401
417
|
OpenSSL::Digest::SHA256.new,
|
402
418
|
real_csrf_token(session),
|
403
|
-
|
419
|
+
identifier
|
404
420
|
)
|
405
421
|
end
|
406
422
|
|
@@ -450,5 +466,33 @@ module ActionController #:nodoc:
|
|
450
466
|
uri = URI.parse(action_path)
|
451
467
|
uri.path.chomp("/")
|
452
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
|
453
497
|
end
|
454
498
|
end
|
@@ -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,18 +384,24 @@ 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
|
391
|
+
|
392
|
+
self
|
347
393
|
end
|
348
394
|
alias_method :each, :each_pair
|
349
395
|
|
350
396
|
# Convert all hashes in values into parameters, then yield each value in
|
351
397
|
# the same way as <tt>Hash#each_value</tt>.
|
352
398
|
def each_value(&block)
|
399
|
+
return to_enum(:each_value) unless block_given?
|
353
400
|
@parameters.each_pair do |key, value|
|
354
401
|
yield convert_hashes_to_parameters(key, value)
|
355
402
|
end
|
403
|
+
|
404
|
+
self
|
356
405
|
end
|
357
406
|
|
358
407
|
# Attribute that keeps track of converted arrays, if any, to avoid double
|
@@ -455,7 +504,7 @@ module ActionController
|
|
455
504
|
if value.present? || value == false
|
456
505
|
value
|
457
506
|
else
|
458
|
-
raise ParameterMissing.new(key)
|
507
|
+
raise ParameterMissing.new(key, @parameters.keys)
|
459
508
|
end
|
460
509
|
end
|
461
510
|
|
@@ -592,7 +641,7 @@ module ActionController
|
|
592
641
|
if block_given?
|
593
642
|
yield
|
594
643
|
else
|
595
|
-
args.fetch(0) { raise ActionController::ParameterMissing.new(key) }
|
644
|
+
args.fetch(0) { raise ActionController::ParameterMissing.new(key, @parameters.keys) }
|
596
645
|
end
|
597
646
|
}
|
598
647
|
)
|
@@ -687,6 +736,23 @@ module ActionController
|
|
687
736
|
self
|
688
737
|
end
|
689
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
|
+
|
690
756
|
# Deletes a key-value pair from +Parameters+ and returns the value. If
|
691
757
|
# +key+ is not found, returns +nil+ (or, with optional code block, yields
|
692
758
|
# +key+ and returns the result). Cf. +#extract!+, which returns the
|
@@ -721,6 +787,28 @@ module ActionController
|
|
721
787
|
end
|
722
788
|
alias_method :delete_if, :reject!
|
723
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
|
+
|
724
812
|
# Returns values that were assigned to the given +keys+. Note that all the
|
725
813
|
# +Hash+ objects will be converted to <tt>ActionController::Parameters</tt>.
|
726
814
|
def values_at(*keys)
|
@@ -767,7 +855,7 @@ module ActionController
|
|
767
855
|
end
|
768
856
|
|
769
857
|
def inspect
|
770
|
-
"
|
858
|
+
"#<#{self.class} #{@parameters} permitted: #{@permitted}>"
|
771
859
|
end
|
772
860
|
|
773
861
|
def self.hook_into_yaml_loading # :nodoc:
|
@@ -809,8 +897,14 @@ module ActionController
|
|
809
897
|
|
810
898
|
attr_writer :permitted
|
811
899
|
|
812
|
-
def
|
813
|
-
@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
|
814
908
|
end
|
815
909
|
|
816
910
|
private
|
@@ -855,15 +949,13 @@ module ActionController
|
|
855
949
|
end
|
856
950
|
end
|
857
951
|
|
858
|
-
def each_element(object)
|
952
|
+
def each_element(object, &block)
|
859
953
|
case object
|
860
954
|
when Array
|
861
955
|
object.grep(Parameters).map { |el| yield el }.compact
|
862
956
|
when Parameters
|
863
|
-
if object.
|
864
|
-
|
865
|
-
object.each { |k, v| hash[k] = yield v }
|
866
|
-
hash
|
957
|
+
if object.nested_attributes?
|
958
|
+
object.each_nested_attribute(&block)
|
867
959
|
else
|
868
960
|
yield object
|
869
961
|
end
|
@@ -891,7 +983,7 @@ module ActionController
|
|
891
983
|
# --- Filtering ----------------------------------------------------------
|
892
984
|
#
|
893
985
|
|
894
|
-
# This is a
|
986
|
+
# This is a list of permitted scalar types that includes the ones
|
895
987
|
# supported in XML and JSON requests.
|
896
988
|
#
|
897
989
|
# This list is in particular used to filter ordinary requests, String goes
|