diff options
Diffstat (limited to 'activesupport/lib')
11 files changed, 139 insertions, 12 deletions
diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index 4675c41936..728d332306 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -148,6 +148,41 @@ module Enumerable map { |element| element[keys.first] } end end + + # Returns a new +Array+ without the blank items. + # Uses Object#blank? for determining if an item is blank. + # + # [1, "", nil, 2, " ", [], {}, false, true].compact_blank + # # => [1, 2, true] + # + # Set.new([nil, "", 1, 2]) + # # => [2, 1] (or [1, 2]) + # + # When called on a +Hash+, returns a new +Hash+ without the blank values. + # + # { a: "", b: 1, c: nil, d: [], e: false, f: true }.compact_blank + # #=> { b: 1, f: true } + def compact_blank + reject(&:blank?) + end +end + +class Hash + # Hash#reject has its own definition, so this needs one too. + def compact_blank #:nodoc: + reject { |_k, v| v.blank? } + end + + # Removes all blank values from the +Hash+ in place and returns self. + # Uses Object#blank? for determining if a value is blank. + # + # h = { a: "", b: 1, c: nil, d: [], e: false, f: true } + # h.compact_blank! + # # => { b: 1, f: true } + def compact_blank! + # use delete_if rather than reject! because it always returns self even if nothing changed + delete_if { |_k, v| v.blank? } + end end class Range #:nodoc: @@ -185,4 +220,15 @@ class Array #:nodoc: super end end + + # Removes all blank elements from the +Array+ in place and returns self. + # Uses Object#blank? for determining if an item is blank. + # + # a = [1, "", nil, 2, " ", [], {}, false, true] + # a.compact_blank! + # # => [1, 2, true] + def compact_blank! + # use delete_if rather than reject! because it always returns self even if nothing changed + delete_if(&:blank?) + end end diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index 54271a3970..14d7f0c484 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -276,6 +276,11 @@ class Module # The delegated method must be public on the target, otherwise it will # raise +DelegationError+. If you wish to instead return +nil+, # use the <tt>:allow_nil</tt> option. + # + # The <tt>marshal_dump</tt> and <tt>_dump</tt> methods are exempt from + # delegation due to possible interference when calling + # <tt>Marshal.dump(object)</tt>, should the delegation target method + # of <tt>object</tt> add or remove instance variables. def delegate_missing_to(target, allow_nil: nil) target = target.to_s target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target) @@ -285,6 +290,7 @@ class Module # It may look like an oversight, but we deliberately do not pass # +include_private+, because they do not get delegated. + return false if name == :marshal_dump || name == :_dump #{target}.respond_to?(name) || super end diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 32cb3a53f4..923d6ad228 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -20,6 +20,9 @@ module ActiveSupport #:nodoc: module Dependencies #:nodoc: extend self + UNBOUND_METHOD_MODULE_NAME = Module.instance_method(:name) + private_constant :UNBOUND_METHOD_MODULE_NAME + mattr_accessor :interlock, default: Interlock.new # :doc: @@ -658,7 +661,7 @@ module ActiveSupport #:nodoc: # Determine if the given constant has been automatically loaded. def autoloaded?(desc) - return false if desc.is_a?(Module) && desc.anonymous? + return false if desc.is_a?(Module) && real_mod_name(desc).nil? name = to_constant_name desc return false unless qualified_const_defined?(name) autoloaded_constants.include?(name) @@ -714,7 +717,7 @@ module ActiveSupport #:nodoc: when String then desc.sub(/^::/, "") when Symbol then desc.to_s when Module - desc.name || + real_mod_name(desc) || raise(ArgumentError, "Anonymous modules have no name to be referenced by") else raise TypeError, "Not a valid constant descriptor: #{desc.inspect}" end @@ -788,6 +791,14 @@ module ActiveSupport #:nodoc: def log(message) logger.debug("autoloading: #{message}") if logger && verbose end + + private + + # Returns the original name of a class or module even if `name` has been + # overridden. + def real_mod_name(mod) + UNBOUND_METHOD_MODULE_NAME.bind(mod).call + end end end diff --git a/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb b/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb index 821e3f971e..f75083a05a 100644 --- a/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb +++ b/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb @@ -28,7 +28,7 @@ module ActiveSupport end def autoloaded?(object) - cpath = object.is_a?(Module) ? object.name : object.to_s + cpath = object.is_a?(Module) ? real_mod_name(object) : object.to_s Rails.autoloaders.main.unloadable_cpath?(cpath) end diff --git a/activesupport/lib/active_support/descendants_tracker.rb b/activesupport/lib/active_support/descendants_tracker.rb index 1dad4f923e..b14842bf67 100644 --- a/activesupport/lib/active_support/descendants_tracker.rb +++ b/activesupport/lib/active_support/descendants_tracker.rb @@ -77,15 +77,17 @@ module ActiveSupport end def <<(klass) - cleanup! @refs << WeakRef.new(klass) end def each - @refs.each do |ref| + @refs.reject! do |ref| yield ref.__getobj__ + false rescue WeakRef::RefError + true end + self end def refs_size diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 5981763f0e..6acf64cb39 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -367,18 +367,20 @@ module ActiveSupport key.kind_of?(Symbol) ? key.to_s : key end - def convert_value(value, options = {}) # :doc: + def convert_value(value, for: nil) # :doc: + conversion = binding.local_variable_get(:for) + if value.is_a? Hash - if options[:for] == :to_hash + if conversion == :to_hash value.to_hash else value.nested_under_indifferent_access end elsif value.is_a?(Array) - if options[:for] != :assignment || value.frozen? + if conversion != :assignment || value.frozen? value = value.dup end - value.map! { |e| convert_value(e, options) } + value.map! { |e| convert_value(e, for: conversion) } else value end diff --git a/activesupport/lib/active_support/logger_thread_safe_level.rb b/activesupport/lib/active_support/logger_thread_safe_level.rb index f16c90cfc6..1775a41492 100644 --- a/activesupport/lib/active_support/logger_thread_safe_level.rb +++ b/activesupport/lib/active_support/logger_thread_safe_level.rb @@ -3,6 +3,7 @@ require "active_support/concern" require "active_support/core_ext/module/attribute_accessors" require "concurrent" +require "fiber" module ActiveSupport module LoggerThreadSafeLevel # :nodoc: @@ -28,7 +29,7 @@ module ActiveSupport end def local_log_id - Thread.current.__id__ + Fiber.current.__id__ end def local_level diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index aa602329ec..dda71b880e 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -218,6 +218,7 @@ module ActiveSupport def finish(name, id, payload) stack = Thread.current[:_event_stack] event = stack.pop + event.payload = payload event.finish! @delegate.call event end diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb index 7ab39c9bfb..24e1ab313a 100644 --- a/activesupport/lib/active_support/notifications/instrumenter.rb +++ b/activesupport/lib/active_support/notifications/instrumenter.rb @@ -52,7 +52,8 @@ module ActiveSupport end class Event - attr_reader :name, :time, :end, :transaction_id, :payload, :children + attr_reader :name, :time, :end, :transaction_id, :children + attr_accessor :payload def self.clock_gettime_supported? # :nodoc: defined?(Process::CLOCK_PROCESS_CPUTIME_ID) && diff --git a/activesupport/lib/active_support/parameter_filter.rb b/activesupport/lib/active_support/parameter_filter.rb index 8e5595babf..e1cd7c46c1 100644 --- a/activesupport/lib/active_support/parameter_filter.rb +++ b/activesupport/lib/active_support/parameter_filter.rb @@ -109,7 +109,12 @@ module ActiveSupport elsif value.is_a?(Hash) value = call(value, parents, original_params) elsif value.is_a?(Array) - value = value.map { |v| v.is_a?(Hash) ? call(v, parents, original_params) : v } + # If we don't pop the current parent it will be duplicated as we + # process each array value. + parents.pop if deep_regexps + value = value.map { |v| value_for_key(key, v, parents, original_params) } + # Restore the parent stack after processing the array. + parents.push(key) if deep_regexps elsif blocks.any? key = key.dup if key.duplicable? value = value.dup if value.duplicable? diff --git a/activesupport/lib/active_support/secure_compare_rotator.rb b/activesupport/lib/active_support/secure_compare_rotator.rb new file mode 100644 index 0000000000..14a0aee947 --- /dev/null +++ b/activesupport/lib/active_support/secure_compare_rotator.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require "active_support/security_utils" +require "active_support/messages/rotator" + +module ActiveSupport + # The ActiveSupport::SecureCompareRotator is a wrapper around +ActiveSupport::SecurityUtils.secure_compare+ + # and allows you to rotate a previously defined value to a new one. + # + # It can be used as follow: + # + # rotator = ActiveSupport::SecureCompareRotator.new('new_production_value') + # rotator.rotate('previous_production_value') + # rotator.secure_compare!('previous_production_value') + # + # One real use case example would be to rotate a basic auth credentials: + # + # class MyController < ApplicationController + # def authenticate_request + # rotator = ActiveSupport::SecureComparerotator.new('new_password') + # rotator.rotate('old_password') + # + # authenticate_or_request_with_http_basic do |username, password| + # rotator.secure_compare!(password) + # rescue ActiveSupport::SecureCompareRotator::InvalidMatch + # false + # end + # end + # end + class SecureCompareRotator + include SecurityUtils + prepend Messages::Rotator + + InvalidMatch = Class.new(StandardError) + + def initialize(value, **_options) + @value = value + end + + def secure_compare!(other_value, on_rotation: @rotation) + secure_compare(@value, other_value) || + run_rotations(on_rotation) { |wrapper| wrapper.secure_compare!(other_value) } || + raise(InvalidMatch) + end + + private + + def build_rotation(previous_value, _options) + self.class.new(previous_value) + end + end +end |