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

require 'contrast/agent/assess/rule/response/auto_complete_rule'
require 'contrast/agent/assess/rule/response/cache_control_header_rule'
require 'contrast/agent/assess/rule/response/click_jacking_header_rule'
require 'contrast/agent/assess/rule/response/csp_header_insecure_rule'
require 'contrast/agent/assess/rule/response/csp_header_missing_rule'
require 'contrast/agent/assess/rule/response/hsts_header_rule'
require 'contrast/agent/assess/rule/response/parameters_pollution_rule'
require 'contrast/agent/assess/rule/response/x_content_type_header_rule'
require 'contrast/agent/assess/rule/response/x_xss_protection_header_rule'
require 'contrast/agent/protect/input_analyzer/input_analyzer'
require 'contrast/components/logger'
require 'contrast/utils/log_utils'
require 'contrast/utils/string_utils'

module Contrast
  module Agent
    # This class extends RequestContexts: this class acts to encapsulate information about the currently
    # executed request, making it available to the Agent for the duration of the request in a standardized
    # and normalized format which the Agent understands.
    module RequestContextExtend
      include Contrast::Utils::CEFLogUtils
      include Contrast::Components::Logger::InstanceMethods
      # Convert the discovered route for this request to appropriate forms and disseminate it to those locations
      # where it is necessary for our route coverage and finding vulnerability discovery features to function.
      #
      # @param route [Contrast::Agent::Reporting::RouteCoverage]
      def append_to_observed_route route
        return unless route

        @observed_route.signature  = route.route
        @observed_route.verb       = route.verb
        @observed_route.url        = route.url if route.url
        @request.observed_route    = @observed_route

        observation = Contrast::Agent::Reporting::RouteDiscoveryObservation.new(route.url, route.verb)
        @discovered_route = Contrast::Agent::Reporting::RouteDiscovery.new(route.route, observation)
        @request.discovered_route = @discovered_route
      end

      # If protect is enabled for this request, examine said request for any possible attack input. If those inputs
      # provided match a rule which should block at the perimeter, that will be raised here.
      #
      # @raise [Contrast::SecurityException]
      def protect_input_analysis
        return false unless ::Contrast::AGENT.enabled?
        return false unless ::Contrast::PROTECT.enabled?
        return false if @do_not_track

        if (ia = Contrast::Agent::Protect::InputAnalyzer.analyse(request))
          # Handle prefilter
          Contrast::Agent::Protect::InputAnalyzer.input_classification(ia, prefilter: true)
          @agent_input_analysis = ia
        else
          logger.trace('Analysis from Agent was empty.')
        end
      rescue Contrast::SecurityException => e
        raise(e)
      rescue StandardError => e
        logger.warn('Unable to extract protect information from request', e)
      end

      # Builds IA only for postfilter rules. If rules during infilter were not triggered there will be
      # no IA for them later to use it in postfilter.
      #
      # @raise [Contrast::SecurityException]
      def protect_postfilter_ia
        return false unless ::Contrast::AGENT.enabled?
        return false unless ::Contrast::PROTECT.enabled?

        # Handle postfilter
        Contrast::Agent::Protect::InputAnalyzer.input_classification(@agent_input_analysis, postfilter: true)
      rescue Contrast::SecurityException => e
        raise(e)
      rescue StandardError => e
        logger.warn('Unable to extract protect information from request - postfilter', e)
      end

      # append anything we've learned to the request seen message this is the sum-total of all inventory information
      # that has been accumulated since the last request
      def extract_after rack_response
        # We must ALWAYS save the response, even if we don't need it here for response sampling. It is used for other
        # vulnerability detection, most notably XSS, and not capturing it may suppress valid findings.
        @response = Contrast::Agent::Response.new(rack_response)
        return unless @sample_res

        Contrast::Agent::Assess::Rule::Response::AutoComplete.new.analyze(@response)
        Contrast::Agent::Assess::Rule::Response::CacheControl.new.analyze(@response)
        Contrast::Agent::Assess::Rule::Response::ClickJacking.new.analyze(@response)
        Contrast::Agent::Assess::Rule::Response::CspHeaderMissing.new.analyze(@response)
        Contrast::Agent::Assess::Rule::Response::CspHeaderInsecure.new.analyze(@response)
        Contrast::Agent::Assess::Rule::Response::HSTSHeader.new.analyze(@response)
        Contrast::Agent::Assess::Rule::Response::ParametersPollution.new.analyze(@response)
        Contrast::Agent::Assess::Rule::Response::XContentType.new.analyze(@response)
        Contrast::Agent::Assess::Rule::Response::XXssProtection.new.analyze(@response)
      rescue StandardError => e
        logger.error('Unable to extract information after request', e)
      end
    end
  end
end