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

module Contrast
  module Agent
    module Assess
      module Policy
        module TriggerValidation
          # Validator used to assert a SSRF finding is actually vulnerable
          # before serializing that finding as a DTM to report to the service.
          module SSRFValidator
            RULE_NAME = 'ssrf'
            URL_PATTERN = %r{(?<protocol>http|https|ftp|sftp|telnet|gopher|rtsp|rtsps|ssh|svn)://(?<host>[^/?]+)(?<path>/?[^?]*)(?<query_string>\?.*)?}i.cs__freeze # rubocop:disable Layout/LineLength
            # The Net::HTTP class validates host format on instantiation. Since
            # our triggers for that class are on the instance, they already
            # have this validation done for them. We do not need to apply the
            # validation in this case.
            PATH_ONLY_PATCH_MARKER = 'Assess:Trigger:Net::HTTP#'

            # A finding is valid for SSRF if the source of the trigger event is
            # a valid URL in which the User controls a section prior to the
            # querystring
            # https://bitbucket.org/contrastsecurity/assess-specifications/src/master/rules/dataflow/server_side_request_forgery.md
            def self.valid? patcher, _object, _ret, args
              return true if patcher.id.to_s.start_with?(PATH_ONLY_PATCH_MARKER)

              url = args[0].to_s
              match = url.match(URL_PATTERN)
              return false unless match

              # It is dangerous for the user to control a section of the URL
              # between the start of the protocol and the beginning of the
              # querystring. If there is no path, then the entire URL is
              # dangerous for the User to control.
              start = match.begin(:protocol)
              finish = match.begin(:path)
              finish ||= url.length

              properties = Contrast::Agent::Assess::Tracker.properties(args[0])
              properties&.any_tags_between?(start, finish)
            end
          end
        end
      end
    end
  end
end