actionpack 7.2.1 → 8.0.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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +86 -105
  3. data/lib/action_controller/api.rb +1 -0
  4. data/lib/action_controller/metal/conditional_get.rb +6 -3
  5. data/lib/action_controller/metal/http_authentication.rb +2 -2
  6. data/lib/action_controller/metal/instrumentation.rb +1 -2
  7. data/lib/action_controller/metal/live.rb +19 -8
  8. data/lib/action_controller/metal/rate_limiting.rb +13 -4
  9. data/lib/action_controller/metal/renderers.rb +2 -1
  10. data/lib/action_controller/metal/streaming.rb +5 -84
  11. data/lib/action_controller/metal/strong_parameters.rb +274 -73
  12. data/lib/action_controller/railtie.rb +1 -1
  13. data/lib/action_controller/test_case.rb +4 -3
  14. data/lib/action_dispatch/http/cache.rb +27 -10
  15. data/lib/action_dispatch/http/content_security_policy.rb +1 -0
  16. data/lib/action_dispatch/http/permissions_policy.rb +2 -0
  17. data/lib/action_dispatch/http/request.rb +4 -2
  18. data/lib/action_dispatch/journey/parser.rb +99 -196
  19. data/lib/action_dispatch/journey/scanner.rb +40 -42
  20. data/lib/action_dispatch/middleware/cookies.rb +4 -2
  21. data/lib/action_dispatch/middleware/debug_exceptions.rb +16 -3
  22. data/lib/action_dispatch/middleware/remote_ip.rb +5 -6
  23. data/lib/action_dispatch/middleware/request_id.rb +2 -1
  24. data/lib/action_dispatch/middleware/ssl.rb +14 -4
  25. data/lib/action_dispatch/railtie.rb +2 -0
  26. data/lib/action_dispatch/routing/inspector.rb +1 -1
  27. data/lib/action_dispatch/routing/mapper.rb +26 -17
  28. data/lib/action_dispatch/routing/route_set.rb +18 -6
  29. data/lib/action_dispatch/system_testing/browser.rb +12 -21
  30. data/lib/action_pack/gem_version.rb +4 -4
  31. metadata +16 -38
  32. data/lib/action_dispatch/journey/parser.y +0 -50
  33. data/lib/action_dispatch/journey/parser_extras.rb +0 -33
@@ -18,16 +18,18 @@ module ActionController
18
18
  #
19
19
  # params = ActionController::Parameters.new(a: {})
20
20
  # params.fetch(:b)
21
- # # => ActionController::ParameterMissing: param is missing or the value is empty: b
21
+ # # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: b
22
22
  # params.require(:a)
23
- # # => ActionController::ParameterMissing: param is missing or the value is empty: a
23
+ # # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: a
24
+ # params.expect(a: [])
25
+ # # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: a
24
26
  class ParameterMissing < KeyError
25
27
  attr_reader :param, :keys # :nodoc:
26
28
 
27
29
  def initialize(param, keys = nil) # :nodoc:
28
30
  @param = param
29
31
  @keys = keys
30
- super("param is missing or the value is empty: #{param}")
32
+ super("param is missing or the value is empty or invalid: #{param}")
31
33
  end
32
34
 
33
35
  if defined?(DidYouMean::Correctable) && defined?(DidYouMean::SpellChecker)
@@ -39,6 +41,15 @@ module ActionController
39
41
  end
40
42
  end
41
43
 
44
+ # Raised from `expect!` when an expected parameter is missing or is of an
45
+ # incompatible type.
46
+ #
47
+ # params = ActionController::Parameters.new(a: {})
48
+ # params.expect!(:a)
49
+ # # => ActionController::ExpectedParameterMissing: param is missing or the value is empty or invalid: a
50
+ class ExpectedParameterMissing < ParameterMissing
51
+ end
52
+
42
53
  # Raised when a supplied parameter is not expected and
43
54
  # ActionController::Parameters.action_on_unpermitted_parameters is set to
44
55
  # `:raise`.
@@ -78,9 +89,12 @@ module ActionController
78
89
  #
79
90
  # Allows you to choose which attributes should be permitted for mass updating
80
91
  # and thus prevent accidentally exposing that which shouldn't be exposed.
81
- # Provides two methods for this purpose: #require and #permit. The former is
82
- # used to mark parameters as required. The latter is used to set the parameter
83
- # as permitted and limit which attributes should be allowed for mass updating.
92
+ #
93
+ # Provides methods for filtering and requiring params:
94
+ #
95
+ # * `expect` to safely permit and require parameters in one step.
96
+ # * `permit` to filter params for mass assignment.
97
+ # * `require` to require a parameter or raise an error.
84
98
  #
85
99
  # params = ActionController::Parameters.new({
86
100
  # person: {
@@ -90,14 +104,14 @@ module ActionController
90
104
  # }
91
105
  # })
92
106
  #
93
- # permitted = params.require(:person).permit(:name, :age)
94
- # permitted # => #<ActionController::Parameters {"name"=>"Francesco", "age"=>22} permitted: true>
95
- # permitted.permitted? # => true
107
+ # permitted = params.expect(person: [:name, :age])
108
+ # permitted # => #<ActionController::Parameters {"name"=>"Francesco", "age"=>22} permitted: true>
96
109
  #
97
110
  # Person.first.update!(permitted)
98
111
  # # => #<Person id: 1, name: "Francesco", age: 22, role: "user">
99
112
  #
100
- # It provides two options that controls the top-level behavior of new instances:
113
+ # Paramaters provides two options that control the top-level behavior of new
114
+ # instances:
101
115
  #
102
116
  # * `permit_all_parameters` - If it's `true`, all the parameters will be
103
117
  # permitted by default. The default is `false`.
@@ -112,8 +126,6 @@ module ActionController
112
126
  # * `:raise` to raise an ActionController::UnpermittedParameters
113
127
  # exception.
114
128
  #
115
- #
116
- #
117
129
  # Examples:
118
130
  #
119
131
  # params = ActionController::Parameters.new
@@ -481,16 +493,16 @@ module ActionController
481
493
  # Otherwise raises ActionController::ParameterMissing:
482
494
  #
483
495
  # ActionController::Parameters.new.require(:person)
484
- # # ActionController::ParameterMissing: param is missing or the value is empty: person
496
+ # # ActionController::ParameterMissing: param is missing or the value is empty or invalid: person
485
497
  #
486
498
  # ActionController::Parameters.new(person: nil).require(:person)
487
- # # ActionController::ParameterMissing: param is missing or the value is empty: person
499
+ # # ActionController::ParameterMissing: param is missing or the value is empty or invalid: person
488
500
  #
489
501
  # ActionController::Parameters.new(person: "\t").require(:person)
490
- # # ActionController::ParameterMissing: param is missing or the value is empty: person
502
+ # # ActionController::ParameterMissing: param is missing or the value is empty or invalid: person
491
503
  #
492
504
  # ActionController::Parameters.new(person: {}).require(:person)
493
- # # ActionController::ParameterMissing: param is missing or the value is empty: person
505
+ # # ActionController::ParameterMissing: param is missing or the value is empty or invalid: person
494
506
  #
495
507
  # When given an array of keys, the method tries to require each one of them in
496
508
  # order. If it succeeds, an array with the respective return values is returned:
@@ -502,23 +514,21 @@ module ActionController
502
514
  #
503
515
  # params = ActionController::Parameters.new(user: {}, profile: {})
504
516
  # user_params, profile_params = params.require([:user, :profile])
505
- # # ActionController::ParameterMissing: param is missing or the value is empty: user
517
+ # # ActionController::ParameterMissing: param is missing or the value is empty or invalid: user
506
518
  #
507
- # Technically this method can be used to fetch terminal values:
519
+ # This method is not recommended for fetching terminal values because it does
520
+ # not permit the values. For example, this can cause problems:
508
521
  #
509
522
  # # CAREFUL
510
523
  # params = ActionController::Parameters.new(person: { name: "Finn" })
511
524
  # name = params.require(:person).require(:name) # CAREFUL
512
525
  #
513
- # but take into account that at some point those ones have to be permitted:
526
+ # It is recommended to use `expect` instead:
514
527
  #
515
528
  # def person_params
516
- # params.require(:person).permit(:name).tap do |person_params|
517
- # person_params.require(:name) # SAFER
518
- # end
529
+ # # params.expect(person: :name).require(:name)
519
530
  # end
520
531
  #
521
- # for example.
522
532
  def require(key)
523
533
  return key.map { |k| require(k) } if key.is_a?(Array)
524
534
  value = self[key]
@@ -536,8 +546,8 @@ module ActionController
536
546
  # This is useful for limiting which attributes should be allowed for mass
537
547
  # updating.
538
548
  #
539
- # params = ActionController::Parameters.new(user: { name: "Francesco", age: 22, role: "admin" })
540
- # permitted = params.require(:user).permit(:name, :age)
549
+ # params = ActionController::Parameters.new(name: "Francesco", age: 22, role: "admin")
550
+ # permitted = params.permit(:name, :age)
541
551
  # permitted.permitted? # => true
542
552
  # permitted.has_key?(:name) # => true
543
553
  # permitted.has_key?(:age) # => true
@@ -567,7 +577,7 @@ module ActionController
567
577
  # `permit` ensures values in the returned structure are permitted scalars and
568
578
  # filters out anything else.
569
579
  #
570
- # You can also use `permit` on nested parameters, like:
580
+ # You can also use `permit` on nested parameters:
571
581
  #
572
582
  # params = ActionController::Parameters.new({
573
583
  # person: {
@@ -587,6 +597,29 @@ module ActionController
587
597
  # permitted[:person][:pets][0][:name] # => "Purplish"
588
598
  # permitted[:person][:pets][0][:category] # => nil
589
599
  #
600
+ # This has the added benefit of rejecting user-modified inputs that send a
601
+ # string when a hash is expected.
602
+ #
603
+ # When followed by `require`, you can both filter and require parameters
604
+ # following the typical pattern of a Rails form. The `expect` method was
605
+ # made specifically for this use case and is the recommended way to require
606
+ # and permit parameters.
607
+ #
608
+ # permitted = params.expect(person: [:name, :age])
609
+ #
610
+ # When using `permit` and `require` separately, pay careful attention to the
611
+ # order of the method calls.
612
+ #
613
+ # params = ActionController::Parameters.new(person: { name: "Martin", age: 40, role: "admin" })
614
+ # permitted = params.permit(person: [:name, :age]).require(:person) # correct
615
+ #
616
+ # When require is used first, it is possible for users of your application to
617
+ # trigger a NoMethodError when the user, for example, sends a string for :person.
618
+ #
619
+ # params = ActionController::Parameters.new(person: "tampered")
620
+ # permitted = params.require(:person).permit(:name, :age) # not recommended
621
+ # # => NoMethodError: undefined method `permit' for an instance of String
622
+ #
590
623
  # Note that if you use `permit` in a key that points to a hash, it won't allow
591
624
  # all the hash. You also need to specify which attributes inside the hash should
592
625
  # be permitted.
@@ -600,13 +633,13 @@ module ActionController
600
633
  # }
601
634
  # })
602
635
  #
603
- # params.require(:person).permit(:contact)
636
+ # params.permit(person: :contact).require(:person)
604
637
  # # => #<ActionController::Parameters {} permitted: true>
605
638
  #
606
- # params.require(:person).permit(contact: :phone)
639
+ # params.permit(person: { contact: :phone }).require(:person)
607
640
  # # => #<ActionController::Parameters {"contact"=>#<ActionController::Parameters {"phone"=>"555-1234"} permitted: true>} permitted: true>
608
641
  #
609
- # params.require(:person).permit(contact: [ :email, :phone ])
642
+ # params.permit(person: { contact: [ :email, :phone ] }).require(:person)
610
643
  # # => #<ActionController::Parameters {"contact"=>#<ActionController::Parameters {"email"=>"none@test.com", "phone"=>"555-1234"} permitted: true>} permitted: true>
611
644
  #
612
645
  # If your parameters specify multiple parameters indexed by a number, you can
@@ -646,20 +679,127 @@ module ActionController
646
679
  # params.permit(person: { '0': [:email], '1': [:phone]}).to_h
647
680
  # # => {"person"=>{"0"=>{"email"=>"none@test.com"}, "1"=>{"phone"=>"555-6789"}}}
648
681
  def permit(*filters)
649
- params = self.class.new
650
-
651
- filters.flatten.each do |filter|
652
- case filter
653
- when Symbol, String
654
- permitted_scalar_filter(params, filter)
655
- when Hash
656
- hash_filter(params, filter)
657
- end
658
- end
682
+ permit_filters(filters, on_unpermitted: self.class.action_on_unpermitted_parameters, explicit_arrays: false)
683
+ end
659
684
 
660
- unpermitted_parameters!(params) if self.class.action_on_unpermitted_parameters
685
+ # `expect` is the preferred way to require and permit parameters.
686
+ # It is safer than the previous recommendation to call `permit` and `require`
687
+ # in sequence, which could allow user triggered 500 errors.
688
+ #
689
+ # `expect` is more strict with types to avoid a number of potential pitfalls
690
+ # that may be encountered with the `.require.permit` pattern.
691
+ #
692
+ # For example:
693
+ #
694
+ # params = ActionController::Parameters.new(comment: { text: "hello" })
695
+ # params.expect(comment: [:text])
696
+ # # => #<ActionController::Parameters { text: "hello" } permitted: true>
697
+ #
698
+ # params = ActionController::Parameters.new(comment: [{ text: "hello" }, { text: "world" }])
699
+ # params.expect(comment: [:text])
700
+ # # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: comment
701
+ #
702
+ # In order to permit an array of parameters, the array must be defined
703
+ # explicitly. Use double array brackets, an array inside an array, to
704
+ # declare that an array of parameters is expected.
705
+ #
706
+ # params = ActionController::Parameters.new(comments: [{ text: "hello" }, { text: "world" }])
707
+ # params.expect(comments: [[:text]])
708
+ # # => [#<ActionController::Parameters { "text" => "hello" } permitted: true>,
709
+ # # #<ActionController::Parameters { "text" => "world" } permitted: true>]
710
+ #
711
+ # params = ActionController::Parameters.new(comments: { text: "hello" })
712
+ # params.expect(comments: [[:text]])
713
+ # # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: comments
714
+ #
715
+ # `expect` is intended to protect against array tampering.
716
+ #
717
+ # params = ActionController::Parameters.new(user: "hack")
718
+ # # The previous way of requiring and permitting parameters will error
719
+ # params.require(:user).permit(:name, pets: [:name]) # wrong
720
+ # # => NoMethodError: undefined method `permit' for an instance of String
721
+ #
722
+ # # similarly with nested parameters
723
+ # params = ActionController::Parameters.new(user: { name: "Martin", pets: { name: "hack" } })
724
+ # user_params = params.require(:user).permit(:name, pets: [:name]) # wrong
725
+ # # user_params[:pets] is expected to be an array but is a hash
726
+ #
727
+ # `expect` solves this by being more strict with types.
728
+ #
729
+ # params = ActionController::Parameters.new(user: "hack")
730
+ # params.expect(user: [ :name, pets: [[:name]] ])
731
+ # # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: user
732
+ #
733
+ # # with nested parameters
734
+ # params = ActionController::Parameters.new(user: { name: "Martin", pets: { name: "hack" } })
735
+ # user_params = params.expect(user: [:name, pets: [[:name]] ])
736
+ # user_params[:pets] # => nil
737
+ #
738
+ # As the examples show, `expect` requires the `:user` key, and any root keys
739
+ # similar to the `.require.permit` pattern. If multiple root keys are
740
+ # expected, they will all be required.
741
+ #
742
+ # params = ActionController::Parameters.new(name: "Martin", pies: [{ type: "dessert", flavor: "pumpkin"}])
743
+ # name, pies = params.expect(:name, pies: [[:type, :flavor]])
744
+ # name # => "Martin"
745
+ # pies # => [#<ActionController::Parameters {"type"=>"dessert", "flavor"=>"pumpkin"} permitted: true>]
746
+ #
747
+ # When called with a hash with multiple keys, `expect` will permit the
748
+ # parameters and require the keys in the order they are given in the hash,
749
+ # returning an array of the permitted parameters.
750
+ #
751
+ # params = ActionController::Parameters.new(subject: { name: "Martin" }, object: { pie: "pumpkin" })
752
+ # subject, object = params.expect(subject: [:name], object: [:pie])
753
+ # subject # => #<ActionController::Parameters {"name"=>"Martin"} permitted: true>
754
+ # object # => #<ActionController::Parameters {"pie"=>"pumpkin"} permitted: true>
755
+ #
756
+ # Besides being more strict about array vs hash params, `expect` uses permit
757
+ # internally, so it will behave similarly.
758
+ #
759
+ # params = ActionController::Parameters.new({
760
+ # person: {
761
+ # name: "Francesco",
762
+ # age: 22,
763
+ # pets: [{
764
+ # name: "Purplish",
765
+ # category: "dogs"
766
+ # }]
767
+ # }
768
+ # })
769
+ #
770
+ # permitted = params.expect(person: [ :name, { pets: [[:name]] } ])
771
+ # permitted.permitted? # => true
772
+ # permitted[:name] # => "Francesco"
773
+ # permitted[:age] # => nil
774
+ # permitted[:pets][0][:name] # => "Purplish"
775
+ # permitted[:pets][0][:category] # => nil
776
+ #
777
+ # An array of permitted scalars may be expected with the following:
778
+ #
779
+ # params = ActionController::Parameters.new(tags: ["rails", "parameters"])
780
+ # permitted = params.expect(tags: [])
781
+ # permitted.permitted? # => true
782
+ # permitted.is_a?(Array) # => true
783
+ # permitted.size # => 2
784
+ #
785
+ def expect(*filters)
786
+ params = permit_filters(filters)
787
+ keys = filters.flatten.flat_map { |f| f.is_a?(Hash) ? f.keys : f }
788
+ values = params.require(keys)
789
+ values.size == 1 ? values.first : values
790
+ end
661
791
 
662
- params.permit!
792
+ # Same as `expect`, but raises an `ActionController::ExpectedParameterMissing`
793
+ # instead of `ActionController::ParameterMissing`. Unlike `expect` which
794
+ # will render a 400 response, `expect!` will raise an exception that is
795
+ # not handled. This is intended for debugging invalid params for an
796
+ # internal API where incorrectly formatted params would indicate a bug
797
+ # in a client library that should be fixed.
798
+ #
799
+ def expect!(*filters)
800
+ expect(*filters)
801
+ rescue ParameterMissing => e
802
+ raise ExpectedParameterMissing.new(e.param, e.keys)
663
803
  end
664
804
 
665
805
  # Returns a parameter for the given `key`. If not found, returns `nil`.
@@ -686,7 +826,7 @@ module ActionController
686
826
  #
687
827
  # params = ActionController::Parameters.new(person: { name: "Francesco" })
688
828
  # params.fetch(:person) # => #<ActionController::Parameters {"name"=>"Francesco"} permitted: false>
689
- # params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty: none
829
+ # params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: none
690
830
  # params.fetch(:none, {}) # => #<ActionController::Parameters {} permitted: false>
691
831
  # params.fetch(:none, "Francesco") # => "Francesco"
692
832
  # params.fetch(:none) { "Francesco" } # => "Francesco"
@@ -999,6 +1139,26 @@ module ActionController
999
1139
  hash
1000
1140
  end
1001
1141
 
1142
+ # Filters self and optionally checks for unpermitted keys
1143
+ def permit_filters(filters, on_unpermitted: nil, explicit_arrays: true)
1144
+ params = self.class.new
1145
+
1146
+ filters.flatten.each do |filter|
1147
+ case filter
1148
+ when Symbol, String
1149
+ # Declaration [:name, "age"]
1150
+ permitted_scalar_filter(params, filter)
1151
+ when Hash
1152
+ # Declaration [{ person: ... }]
1153
+ hash_filter(params, filter, on_unpermitted:, explicit_arrays:)
1154
+ end
1155
+ end
1156
+
1157
+ unpermitted_parameters!(params, on_unpermitted:)
1158
+
1159
+ params.permit!
1160
+ end
1161
+
1002
1162
  private
1003
1163
  def new_instance_with_inherited_permitted_status(hash)
1004
1164
  self.class.new(hash, @logging_context).tap do |new_instance|
@@ -1088,23 +1248,45 @@ module ActionController
1088
1248
  end
1089
1249
  end
1090
1250
 
1091
- def each_element(object, filter, &block)
1251
+ # When an array is expected, you must specify an array explicitly
1252
+ # using the following format:
1253
+ #
1254
+ # params.expect(comments: [[:flavor]])
1255
+ #
1256
+ # Which will match only the following array formats:
1257
+ #
1258
+ # { pies: [{ flavor: "rhubarb" }, { flavor: "apple" }] }
1259
+ # { pies: { "0" => { flavor: "key lime" }, "1" => { flavor: "mince" } } }
1260
+ #
1261
+ # When using `permit`, arrays are specified the same way as hashes:
1262
+ #
1263
+ # params.expect(pies: [:flavor])
1264
+ #
1265
+ # In this case, `permit` would also allow matching with a hash (or vice versa):
1266
+ #
1267
+ # { pies: { flavor: "cherry" } }
1268
+ #
1269
+ def array_filter?(filter)
1270
+ filter.is_a?(Array) && filter.size == 1 && filter.first.is_a?(Array)
1271
+ end
1272
+
1273
+ # Called when an explicit array filter is encountered.
1274
+ def each_array_element(object, filter, &block)
1092
1275
  case object
1093
1276
  when Array
1094
1277
  object.grep(Parameters).filter_map(&block)
1095
1278
  when Parameters
1096
1279
  if object.nested_attributes? && !specify_numeric_keys?(filter)
1097
1280
  object.each_nested_attribute(&block)
1098
- else
1099
- yield object
1100
1281
  end
1101
1282
  end
1102
1283
  end
1103
1284
 
1104
- def unpermitted_parameters!(params)
1285
+ def unpermitted_parameters!(params, on_unpermitted: self.class.action_on_unpermitted_parameters)
1286
+ return unless on_unpermitted
1105
1287
  unpermitted_keys = unpermitted_keys(params)
1106
1288
  if unpermitted_keys.any?
1107
- case self.class.action_on_unpermitted_parameters
1289
+ case on_unpermitted
1108
1290
  when :log
1109
1291
  name = "unpermitted_parameters.action_controller"
1110
1292
  ActiveSupport::Notifications.instrument(name, keys: unpermitted_keys, context: @logging_context)
@@ -1174,45 +1356,63 @@ module ActionController
1174
1356
  end
1175
1357
  end
1176
1358
 
1177
- def array_of_permitted_scalars?(value)
1178
- if value.is_a?(Array) && value.all? { |element| permitted_scalar?(element) }
1179
- yield value
1180
- end
1181
- end
1182
-
1183
1359
  def non_scalar?(value)
1184
1360
  value.is_a?(Array) || value.is_a?(Parameters)
1185
1361
  end
1186
1362
 
1187
1363
  EMPTY_ARRAY = [] # :nodoc:
1188
1364
  EMPTY_HASH = {} # :nodoc:
1189
- def hash_filter(params, filter)
1365
+ def hash_filter(params, filter, on_unpermitted: self.class.action_on_unpermitted_parameters, explicit_arrays: false)
1190
1366
  filter = filter.with_indifferent_access
1191
1367
 
1192
1368
  # Slicing filters out non-declared keys.
1193
1369
  slice(*filter.keys).each do |key, value|
1194
1370
  next unless value
1195
1371
  next unless has_key? key
1372
+ result = permit_value(value, filter[key], on_unpermitted:, explicit_arrays:)
1373
+ params[key] = result unless result.nil?
1374
+ end
1375
+ end
1196
1376
 
1197
- if filter[key] == EMPTY_ARRAY
1198
- # Declaration { comment_ids: [] }.
1199
- array_of_permitted_scalars?(self[key]) do |val|
1200
- params[key] = val
1201
- end
1202
- elsif filter[key] == EMPTY_HASH
1203
- # Declaration { preferences: {} }.
1204
- if value.is_a?(Parameters)
1205
- params[key] = permit_any_in_parameters(value)
1206
- end
1207
- elsif non_scalar?(value)
1208
- # Declaration { user: :name } or { user: [:name, :age, { address: ... }] }.
1209
- params[key] = each_element(value, filter[key]) do |element|
1210
- element.permit(*Array.wrap(filter[key]))
1211
- end
1212
- end
1377
+ def permit_value(value, filter, on_unpermitted:, explicit_arrays:)
1378
+ if filter == EMPTY_ARRAY # Declaration { comment_ids: [] }.
1379
+ permit_array_of_scalars(value)
1380
+ elsif filter == EMPTY_HASH # Declaration { preferences: {} }.
1381
+ permit_hash(value, filter, on_unpermitted:, explicit_arrays:)
1382
+ elsif array_filter?(filter) # Declaration { comments: [[:text]] }
1383
+ permit_array_of_hashes(value, filter.first, on_unpermitted:, explicit_arrays:)
1384
+ elsif explicit_arrays # Declaration { user: { address: ... } } or { user: [:name, ...] } (only allows hash value)
1385
+ permit_hash(value, filter, on_unpermitted:, explicit_arrays:)
1386
+ elsif non_scalar?(value) # Declaration { user: { address: ... } } or { user: [:name, ...] }
1387
+ permit_hash_or_array(value, filter, on_unpermitted:, explicit_arrays:)
1213
1388
  end
1214
1389
  end
1215
1390
 
1391
+ def permit_array_of_scalars(value)
1392
+ value if value.is_a?(Array) && value.all? { |element| permitted_scalar?(element) }
1393
+ end
1394
+
1395
+ def permit_array_of_hashes(value, filter, on_unpermitted:, explicit_arrays:)
1396
+ each_array_element(value, filter) do |element|
1397
+ element.permit_filters(Array.wrap(filter), on_unpermitted:, explicit_arrays:)
1398
+ end
1399
+ end
1400
+
1401
+ def permit_hash(value, filter, on_unpermitted:, explicit_arrays:)
1402
+ return unless value.is_a?(Parameters)
1403
+
1404
+ if filter == EMPTY_HASH
1405
+ permit_any_in_parameters(value)
1406
+ else
1407
+ value.permit_filters(Array.wrap(filter), on_unpermitted:, explicit_arrays:)
1408
+ end
1409
+ end
1410
+
1411
+ def permit_hash_or_array(value, filter, on_unpermitted:, explicit_arrays:)
1412
+ permit_array_of_hashes(value, filter, on_unpermitted:, explicit_arrays:) ||
1413
+ permit_hash(value, filter, on_unpermitted:, explicit_arrays:)
1414
+ end
1415
+
1216
1416
  def permit_any_in_parameters(params)
1217
1417
  self.class.new.tap do |sanitized|
1218
1418
  params.each do |key, value|
@@ -1287,7 +1487,7 @@ module ActionController
1287
1487
  # # list between create and update. Also, you can specialize this method
1288
1488
  # # with per-user checking of permissible attributes.
1289
1489
  # def person_params
1290
- # params.require(:person).permit(:name, :age)
1490
+ # params.expect(person: [:name, :age])
1291
1491
  # end
1292
1492
  # end
1293
1493
  #
@@ -1314,11 +1514,12 @@ module ActionController
1314
1514
  # # It's mandatory to specify the nested attributes that should be permitted.
1315
1515
  # # If you use `permit` with just the key that points to the nested attributes hash,
1316
1516
  # # it will return an empty hash.
1317
- # params.require(:person).permit(:name, :age, pets_attributes: [ :id, :name, :category ])
1517
+ # params.expect(person: [ :name, :age, pets_attributes: [ :id, :name, :category ] ])
1318
1518
  # end
1319
1519
  # end
1320
1520
  #
1321
- # See ActionController::Parameters.require and
1521
+ # See ActionController::Parameters.expect,
1522
+ # See ActionController::Parameters.require, and
1322
1523
  # ActionController::Parameters.permit for more information.
1323
1524
  module StrongParameters
1324
1525
  # Returns a new ActionController::Parameters object that has been instantiated
@@ -123,7 +123,7 @@ module ActionController
123
123
  app.config.active_record.query_log_tags |= [:action]
124
124
 
125
125
  ActiveSupport.on_load(:active_record) do
126
- ActiveRecord::QueryLogs.taggings.merge!(
126
+ ActiveRecord::QueryLogs.taggings = ActiveRecord::QueryLogs.taggings.merge(
127
127
  controller: ->(context) { context[:controller]&.controller_name },
128
128
  action: ->(context) { context[:controller]&.action_name },
129
129
  namespaced_controller: ->(context) {
@@ -22,6 +22,8 @@ module ActionController
22
22
  # database on the main thread, so they could open a txn, then the controller
23
23
  # thread will open a new connection and try to access data that's only visible
24
24
  # to the main thread's txn. This is the problem in #23483.
25
+ alias_method :original_new_controller_thread, :new_controller_thread
26
+
25
27
  silence_redefinition_of_method :new_controller_thread
26
28
  def new_controller_thread # :nodoc:
27
29
  yield
@@ -427,9 +429,7 @@ module ActionController
427
429
  # Note that the request method is not verified. The different methods are
428
430
  # available to make the tests more expressive.
429
431
  def get(action, **args)
430
- res = process(action, method: "GET", **args)
431
- cookies.update res.cookies
432
- res
432
+ process(action, method: "GET", **args)
433
433
  end
434
434
 
435
435
  # Simulate a POST request with the given parameters and set/volley the response.
@@ -637,6 +637,7 @@ module ActionController
637
637
  unless @request.cookie_jar.committed?
638
638
  @request.cookie_jar.write(@response)
639
639
  cookies.update(@request.cookie_jar.instance_variable_get(:@cookies))
640
+ cookies.update(@response.cookies)
640
641
  end
641
642
  end
642
643
  @response.prepare!
@@ -9,6 +9,8 @@ module ActionDispatch
9
9
  HTTP_IF_MODIFIED_SINCE = "HTTP_IF_MODIFIED_SINCE"
10
10
  HTTP_IF_NONE_MATCH = "HTTP_IF_NONE_MATCH"
11
11
 
12
+ mattr_accessor :strict_freshness, default: false
13
+
12
14
  def if_modified_since
13
15
  if since = get_header(HTTP_IF_MODIFIED_SINCE)
14
16
  Time.rfc2822(since) rescue nil
@@ -34,19 +36,32 @@ module ActionDispatch
34
36
  end
35
37
  end
36
38
 
37
- # Check response freshness (`Last-Modified` and ETag) against request
38
- # `If-Modified-Since` and `If-None-Match` conditions. If both headers are
39
- # supplied, both must match, or the request is not considered fresh.
39
+ # Check response freshness (`Last-Modified` and `ETag`) against request
40
+ # `If-Modified-Since` and `If-None-Match` conditions.
41
+ # If both headers are supplied, based on configuration, either `ETag` is preferred over `Last-Modified`
42
+ # or both are considered equally. You can adjust the preference with
43
+ # `config.action_dispatch.strict_freshness`.
44
+ # Reference: http://tools.ietf.org/html/rfc7232#section-6
40
45
  def fresh?(response)
41
- last_modified = if_modified_since
42
- etag = if_none_match
46
+ if Request.strict_freshness
47
+ if if_none_match
48
+ etag_matches?(response.etag)
49
+ elsif if_modified_since
50
+ not_modified?(response.last_modified)
51
+ else
52
+ false
53
+ end
54
+ else
55
+ last_modified = if_modified_since
56
+ etag = if_none_match
43
57
 
44
- return false unless last_modified || etag
58
+ return false unless last_modified || etag
45
59
 
46
- success = true
47
- success &&= not_modified?(response.last_modified) if last_modified
48
- success &&= etag_matches?(response.etag) if etag
49
- success
60
+ success = true
61
+ success &&= not_modified?(response.last_modified) if last_modified
62
+ success &&= etag_matches?(response.etag) if etag
63
+ success
64
+ end
50
65
  end
51
66
  end
52
67
 
@@ -171,6 +186,7 @@ module ActionDispatch
171
186
  PUBLIC = "public"
172
187
  PRIVATE = "private"
173
188
  MUST_REVALIDATE = "must-revalidate"
189
+ IMMUTABLE = "immutable"
174
190
 
175
191
  def handle_conditional_get!
176
192
  # Normally default cache control setting is handled by ETag middleware. But, if
@@ -221,6 +237,7 @@ module ActionDispatch
221
237
  options << MUST_REVALIDATE if control[:must_revalidate]
222
238
  options << "stale-while-revalidate=#{stale_while_revalidate.to_i}" if stale_while_revalidate
223
239
  options << "stale-if-error=#{stale_if_error.to_i}" if stale_if_error
240
+ options << IMMUTABLE if control[:immutable]
224
241
  options.concat(extras) if extras
225
242
  end
226
243
 
@@ -126,6 +126,7 @@ module ActionDispatch # :nodoc:
126
126
  MAPPINGS = {
127
127
  self: "'self'",
128
128
  unsafe_eval: "'unsafe-eval'",
129
+ wasm_unsafe_eval: "'wasm-unsafe-eval'",
129
130
  unsafe_hashes: "'unsafe-hashes'",
130
131
  unsafe_inline: "'unsafe-inline'",
131
132
  none: "'none'",
@@ -86,12 +86,14 @@ module ActionDispatch # :nodoc:
86
86
  ambient_light_sensor: "ambient-light-sensor",
87
87
  autoplay: "autoplay",
88
88
  camera: "camera",
89
+ display_capture: "display-capture",
89
90
  encrypted_media: "encrypted-media",
90
91
  fullscreen: "fullscreen",
91
92
  geolocation: "geolocation",
92
93
  gyroscope: "gyroscope",
93
94
  hid: "hid",
94
95
  idle_detection: "idle-detection",
96
+ keyboard_map: "keyboard-map",
95
97
  magnetometer: "magnetometer",
96
98
  microphone: "microphone",
97
99
  midi: "midi",