# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'contrast/agent/assess/policy/propagation_node' require 'contrast/components/logger' require 'contrast/components/scope' require 'contrast/agent/assess/events/event_data' module Contrast module Extension module Assess # This Class provides us with a way to invoke Regexp propagation for those # methods which are too complex to fit into one of the standard # Contrast::Agent::Assess::Policy::Propagator molds without cluttering up the # Regexp Class or exposing our methods there. class RegexpPropagator extend Contrast::Components::Logger::InstanceMethods extend Contrast::Components::Scope::InstanceMethods REGEXP_EQUAL_SQUIGGLE_HASH = { 'id' => 'regexp_100', 'class_name' => 'Regexp', 'instance_method' => true, 'method_visibility' => 'public', 'method_name' => '=~', 'action' => 'CUSTOM', 'source' => 'P0', 'target' => 'R', 'patch_class' => 'Contrast::Extension::Assess::RegexpPropagator', 'patch_method' => 'track_equal_squiggle' }.cs__freeze REGEXP_EQUAL_SQUIGGLE_NODE = Contrast::Agent::Assess::Policy::PropagationNode.new(REGEXP_EQUAL_SQUIGGLE_HASH) private_constant :REGEXP_EQUAL_SQUIGGLE_HASH private_constant :REGEXP_EQUAL_SQUIGGLE_NODE class << self def track_equal_squiggle info_hash return unless ::Contrast::ASSESS.enabled? # Because we have a special case for this propagation, # it falls out of regular scoping. As such, any patch to the `=~` method # that goes through normal channels, like that for the redos rule, # will force this to be in a scope of 1 (instead of the normal 0). # As such, a scope of 1 here indicates that, # so we know that we're in the top level call for this method. # normal patch [-alias-]> special case patch [-alias-]> original method # TODO: RUBY-686 return if scope_for_current_ec.instance_variable_get(:@contrast_scope) > 1 target = info_hash[:back_ref] with_contrast_scope do return unless (result = info_hash[:result]) return unless (string = info_hash[:string]) return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target)) properties.splat_from(string, target) event_data = Contrast::Agent::Assess::Events::EventData.new(REGEXP_EQUAL_SQUIGGLE_NODE, target, self, result, [string]) properties.build_event(event_data) end rescue Exception => e # rubocop:disable Lint/RescueException logger.error('Unable to propagate during Regexp#=~', e) end end end end end end