# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'contrast/agent/patching/policy/patch' require 'contrast/agent/patching/policy/patcher' require 'contrast/components/scope' require 'contrast/agent/assess/events/event_data' require 'cs__assess_array/cs__assess_array' module Contrast module Extension module Assess # This is our patch of the Array class required to handle propagation # Disclaimer: there may be a better way, but we're in a 'get it work' state. # Hopefully, we'll be in a 'get it right' state soon. class ArrayPropagator # rubocop:disable Style/StaticClass extend Contrast::Components::Scope::InstanceMethods ARRAY_JOIN_HASH = { 'class_name' => 'Array', 'instance_method' => true, 'method_visibility' => 'public', 'method_name' => 'join', 'action' => 'CUSTOM', 'source' => 'O', 'target' => 'R', 'patch_class' => 'NOOP', 'patch_method' => 'cs__track_join' }.cs__freeze ARRAY_JOIN_NODE = Contrast::Agent::Assess::Policy::PropagationNode.new(ARRAY_JOIN_HASH) class << self # When you call join, they use an internal thing, so there's no good way to get at the thing being returned. # Multiple Strings are appended with the #join method. Because that # operation happens in C, we have to do it here rather than rely on the # patch of our String append or concat methods. def cs__track_join ary, separator, ret return ret unless ary&.any? { |element| Contrast::Agent::Assess::Tracker.tracked?(element) } return ret if Contrast::Agent::Patching::Policy::Patch.skip_assess_analysis? with_contrast_scope do return ret unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret)) shift = 0 separator_length = separator.nil? ? 0 : separator.to_s.length parent_events = [] ary.compact.each do |obj| properties.copy_from(obj, ret, shift) shift += obj.to_s.length parent_event = Contrast::Agent::Assess::Tracker.properties(obj)&.event parent_events << parent_event if parent_event shift += separator_length end return ret unless Contrast::Agent::Assess::Tracker.tracked?(ret) properties.cleanup_tags event_data = Contrast::Agent::Assess::Events::EventData.new(ARRAY_JOIN_NODE, ret, ary, ret, [separator]) properties.build_event(event_data) properties.event.instance_variable_set(:@_parent_events, parent_events) ret end end end end # Helper module to call cs__join module ContrastArray def join *args cs__join(*args) super(*args) end end end end end