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

require 'contrast/agent/protect/rule/cmd_injection'
require 'contrast/agent/protect/policy/applies_deserialization_rule'
require 'contrast/agent/protect/policy/rule_applicator'

module Contrast
  module Agent
    module Protect
      module Policy
        # This Module is how we apply the Command Injection rule. It is called
        # from our patches of the targeted methods in which command execution
        # occurs. It is responsible for deciding if the infilter methods of the
        # rule should be invoked.
        # In addition, b/c of the nature of Deserialization's sand boxing
        # function, this Module's apply methods call through to the
        # {#apply_deserialization_command_check} method of the
        # Deserialization applicator.
        module AppliesCommandInjectionRule
          extend Contrast::Agent::Protect::Policy::RuleApplicator

          CS__SEMICOLON = '; '

          class << self
            def invoke method, _exception, _properties, object, args
              return unless valid_command?(args)

              command = build_command(args)
              Contrast::Agent::Protect::Policy::AppliesDeserializationRule.apply_deserialization_command_check(command)
              return if skip_analysis?

              clazz = object.is_a?(Module) ? object : object.cs__class
              class_name = clazz.cs__name
              # Get the ia for current rule:
              apply_classification(rule_name, Contrast::Agent::REQUEST_TRACKER.current)
              rule.infilter(Contrast::Agent::REQUEST_TRACKER.current, class_name, method, command)
              # invoke cmdi sub-rules.
              rule.sub_rules.each do |sub_rule|
                sub_rule.infilter(Contrast::Agent::REQUEST_TRACKER.current, class_name, method, command)
              end
            end

            protected

            def rule_name
              Contrast::Agent::Protect::Rule::CmdInjection::NAME
            end

            private

            def valid_command? command
              command && (command.is_a?(String) || command.is_a?(Array))
            end

            def build_command command
              return command if command.is_a?(String)

              command = command.drop(1) if command.length > 1
              command.join(CS__SEMICOLON)
            end
          end
        end
      end
    end
  end
end