# Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true module Contrast module Agent module Assess module Policy module Propagator # Propagation that results in all the tags of the source being applied to the totality of the target and then # those sections which have been removed from the target are removed from the tags. The target's preexisting # tags are also updated by this removal. class Remove < Contrast::Agent::Assess::Policy::Propagator::Base class << self # For the source, append its tags to the target. Once the tag is applied, remove the section that was # removed by the delete. Unlike additive propagation, this currently only supports one source. def propagate propagation_node, preshift, target return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target)) source = find_source(propagation_node.sources[0], preshift) properties.copy_from(source, target, 0, propagation_node.untags) handle_removal(propagation_node, source, target) end def handle_removal propagation_node, source, target return unless source return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target)) source_string = source.is_a?(String) ? source : source.to_s # If the lengths are the same, we should just copy the tags because nothing was removed, but a new # instance could have been created. copy_from will handle the case where the source is the target. if source_string.length == target.length properties.copy_from(source, target, 0, propagation_node.untags) return end source_chars = source_string.chars source_idx = 0 target_chars = target.chars target_idx = 0 remove_ranges = [] start = nil # loop over the target, the result of the delete every range of characters that it differs from the # source represents a section that was deleted. these sections need to have their tags updated target_len = target_chars.length while target_idx < target_len target_char = target_chars[target_idx] source_char = source_chars[source_idx] if target_char == source_char target_idx += 1 if start remove_ranges << (start...source_idx) start = nil end else start ||= source_idx end source_idx += 1 end # once we're done looping over the target, anything left over is extra from the source that was # deleted. tags applying to it need to be removed. remove_ranges << (source_idx...source_chars.length) if source_idx != source_chars.length # handle deleting the removed ranges properties.delete_tags_at_ranges(remove_ranges) end end end end end end end end