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

require 'contrast/utils/object_share'
require 'contrast/agent/reporting/input_analysis/input_type'
require 'contrast/agent/protect/rule/sqli'
require 'contrast/agent/reporting/input_analysis/score_level'
require 'contrast/agent/protect/rule/sqli/sqli_worth_watching'
require 'contrast/agent/protect/input_analyzer/input_analyzer'
require 'contrast/utils/input_classification'
require 'contrast/components/logger'

module Contrast
  module Agent
    module Protect
      module Rule
        # This module will do the Input Classification stage of SQLI rule
        # as a result input would be marked as WORTHWATCHING or IGNORE,
        # to be analyzed at the sink level.
        module SqliInputClassification
          class << self
            include InputClassificationBase
            include Contrast::Agent::Protect::Rule::SqliWorthWatching
            include Contrast::Components::Logger::InstanceMethods

            WORTHWATCHING_MATCH = 'sqli-worth-watching-v2'
            SQLI_KEYS_NEEDED = [
              COOKIE_VALUE, PARAMETER_VALUE, JSON_VALUE, MULTIPART_VALUE, XML_VALUE, DWR_VALUE
            ].cs__freeze

            # Input Classification stage is done to determine if an user input is
            # WORTHWATCHING or to be ignored.
            #
            # @param input_type [Contrast::Agent::Reporting::InputType] The type of the user input.
            # @param value [String, Array<String>] the value of the input.
            # @param input_analysis [Contrast::Agent::Reporting::InputAnalysis] Holds all the results from the
            #                                                       agent analysis from the current
            #                                                       Request.
            # @return ia [Contrast::Agent::Reporting::InputAnalysis] with updated results.
            def classify input_type, value, input_analysis
              return unless Contrast::Agent::Protect::Rule::Sqli::APPLICABLE_USER_INPUTS.include?(input_type)
              return unless super

              rule_id = Contrast::Agent::Protect::Rule::Sqli::NAME

              # double check the input to avoid calling match? on array
              Array(value).each do |val|
                Array(val).each do |v|
                  input_analysis.results << sqli_create_new_input_result(input_analysis.request, rule_id, input_type, v)
                end
              end

              input_analysis
            rescue StandardError => e
              logger.debug('An Error was recorded in the input classification of the sqli.')
              logger.debug(e)
            end

            private

            # This methods checks if input is tagged WORTHWATCHING or IGNORE matches value with it's
            # key if needed and Creates new isntance of InputAnalysisResult.
            #
            # @param request [Contrast::Agent::Request] the current request context.
            # @param rule_id [String] The name of the Protect Rule.
            # @param input_type [Contrast::Agent::Reporting::InputType] The type of the user input.
            # @param value [String, Array<String>] the value of the input.
            #
            # @return res [Contrast::Agent::Reporting::InputAnalysisResult]
            def sqli_create_new_input_result request, rule_id, input_type, value
              ia_result = new_ia_result(rule_id, input_type, request.path, value)
              if sqli_worth_watching?(value)
                ia_result.ids << WORTHWATCHING_MATCH
                ia_result.score_level = WORTHWATCHING
                ia_result
              else
                ia_result.score_level = IGNORE
              end

              add_needed_key(request, ia_result, input_type, value) if SQLI_KEYS_NEEDED.include?(input_type)
              ia_result
            end
          end
        end
      end
    end
  end
end