# Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
# frozen_string_literal: true

require 'contrast/agent/protect/rule/base'
require 'contrast/utils/stack_trace_utils'
require 'contrast/utils/object_share'
require 'contrast/components/logger'
require 'contrast/agent/reporting/input_analysis/input_type'
require 'contrast/agent_lib/interface'
require 'contrast/agent/protect/rule/cmdi/cmdi_chained_command'
require 'contrast/agent/protect/rule/cmdi/cmdi_dangerous_path'
require 'contrast/agent/protect/rule/cmdi/cmdi_base_rule'
require 'contrast/agent/protect/rule/cmdi/cmdi_backdoors'

module Contrast
  module Agent
    module Protect
      module Rule
        # The Ruby implementation of the Protect Command Injection rule.
        class CmdInjection < Contrast::Agent::Protect::Rule::CmdiBaseRule
          include Contrast::Components::Logger::InstanceMethods
          include Contrast::Agent::Reporting::InputType
          NAME = 'cmd-injection'

          def rule_name
            NAME
          end

          # Sub-rules forwarders:

          # @return [Contrast::Agent::Protect::Rule::CmdiBackdoors]
          def command_backdoors
            @_command_backdoors ||= Contrast::Agent::Protect::Rule::CmdiBackdoors.new
          end

          # @return [Contrast::Agent::Protect::Rule::CmdiChainedCommand]
          def semantic_chained_commands
            @_semantic_chained_commands ||= Contrast::Agent::Protect::Rule::CmdiChainedCommand.new
          end

          def semantic_dangerous_paths
            @_semantic_dangerous_paths ||= Contrast::Agent::Protect::Rule::CmdiDangerousPath.new
          end

          # Array of sub_rules:
          #
          # @return [Array]
          def sub_rules
            @_sub_rules ||= [command_backdoors, semantic_chained_commands, semantic_dangerous_paths].cs__freeze
          end

          def applicable_user_inputs
            APPLICABLE_USER_INPUTS
          end

          # CMDI infilter:
          #
          # @param context [Contrast::Agent::RequestContext] current request context
          # @param classname [String] Name of the class
          # @param method [String] name of the method triggering the rule
          # @param command [String] potential dangerous command executed.
          # @raise [Contrast::SecurityException] if the rule mode is set
          # to BLOCK and valid cdmi is detected.
          def infilter context, classname, method, command
            return unless infilter?(context)

            ia_results = gather_ia_results(context)
            return if ia_results.empty?

            if ::Contrast::APP_CONTEXT.in_new_process?
              logger.trace('Running cmd-injection infilter within new process - creating new context')
              context = Contrast::Agent::RequestContext.new(context.request.rack_request)
              Contrast::Agent::REQUEST_TRACKER.update_current_context(context)
            end

            result = find_attacker_with_results(context, command, ia_results,
                                                **{ classname: classname, method: method })
            result ||= report_command_execution(context, command, **{ classname: classname, method: method })
            return unless result

            append_to_activity(context, result)
            record_triggered(context)
            # Raise cmdi error
            raise_error(classname, method) if blocked_violation?(result)
          end
        end
      end
    end
  end
end