actionpack 7.2.1.1 → 8.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +86 -115
  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 +6 -3
  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/filter_parameters.rb +4 -9
  17. data/lib/action_dispatch/http/filter_redirect.rb +2 -9
  18. data/lib/action_dispatch/http/permissions_policy.rb +2 -0
  19. data/lib/action_dispatch/http/request.rb +4 -2
  20. data/lib/action_dispatch/journey/parser.rb +99 -196
  21. data/lib/action_dispatch/journey/scanner.rb +40 -42
  22. data/lib/action_dispatch/middleware/cookies.rb +4 -2
  23. data/lib/action_dispatch/middleware/debug_exceptions.rb +16 -3
  24. data/lib/action_dispatch/middleware/remote_ip.rb +5 -6
  25. data/lib/action_dispatch/middleware/request_id.rb +2 -1
  26. data/lib/action_dispatch/middleware/ssl.rb +14 -4
  27. data/lib/action_dispatch/railtie.rb +2 -0
  28. data/lib/action_dispatch/routing/inspector.rb +1 -1
  29. data/lib/action_dispatch/routing/mapper.rb +26 -17
  30. data/lib/action_dispatch/routing/route_set.rb +18 -6
  31. data/lib/action_dispatch/system_testing/browser.rb +12 -21
  32. data/lib/action_pack/gem_version.rb +4 -4
  33. metadata +12 -34
  34. data/lib/action_dispatch/journey/parser.y +0 -50
  35. 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'",
@@ -68,17 +68,12 @@ module ActionDispatch
68
68
  ActiveSupport::ParameterFilter.new(filters)
69
69
  end
70
70
 
71
+ KV_RE = "[^&;=]+"
72
+ PAIR_RE = %r{(#{KV_RE})=(#{KV_RE})}
71
73
  def filtered_query_string # :doc:
72
- parts = query_string.split(/([&;])/)
73
- filtered_parts = parts.map do |part|
74
- if part.include?("=")
75
- key, value = part.split("=", 2)
76
- parameter_filter.filter(key => value).first.join("=")
77
- else
78
- part
79
- end
74
+ query_string.gsub(PAIR_RE) do |_|
75
+ parameter_filter.filter($1 => $2).first.join("=")
80
76
  end
81
- filtered_parts.join("")
82
77
  end
83
78
  end
84
79
  end