# 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 Telemetry
      module TelemetryException
        # Here will be all known gems to obfuscate during the building of TelemetryExceptions
        module Obfuscate
          # List of known gems to be third parties not containing any
          # user sensitive data.
          KNOWN_GEMS = %w[
            activesupport redis-activesupport redis builder dry-types rails rails-html-sanitizer rails-observers sinatra
            benchmark-ips bundler climate_control execjs factory_bot debride fake_ftp fasterer flay openssl
            parallel_tests contrast contrast-agent ruby pry-byebug byebug pry resolv ougai protobuf async nokogiri
            benchmark grape-order grape-activerecord newrelic newrelic-grape grape-rails-cache grape-msgpack
            grape-batch rspec rake rubocop grape-jbuilder rack-test rack-protection minitest grape-instrumentation
            minitest-global_expectations rack-proxy rack-attack rack-ssl grape-cancan grape-attack grape-rake-tasks
            grape-route-helpers benchmark-memory grape-rabl grape-kaminari grape-path-helpers grape-swagger-entity
            grape-swagger-rails grape-roar newrelic-rake grapes-wagger-representative grape-forgery_protection
            grape-papertrail grape-app grape-middleware-logger rack-protection rake-compile rhino rspec-benchmark
            rspec_junit_formatter rspec-rails rake-performance rubocop-rails rubocop-rake rubocop-rspec
            ruby-debug-ide simplecov sqlite steep tilt tzinfo-data warning xpath sinatra-activerecord sinatra-param
            sinatra-partial sinatra-flash sinatra-reloader sinatra-sinatra rake_fly rack rack-accept grape grape-entity
            sinatra-advanced-routes sinatra-warden grape_logging grape-swagger sinatra-namespace sinatra-resource
            sinatra-hashfix sinatra-instrumentation sinatra-helpers redis-rack sinatra-support sinatra-assetpack
            sinatra-router sinatra-cross_origin sinatra_more sinatra-jsonp faraday-rack zlib mustermann mustermann-grape
            rspec-expectations rspec-core rspec-mocks rspec-sidekiq
          ].cs__freeze
          KNOWN_ERRORS = %w[
            ActiveModel Error Errors Resolv Rspec ActiveRecord nil NilClass NoMemoryError ScriptError
            LoadError NotImplementedError SyntaxError SecurityError SignalException Interrupt
            StandardError ArgumentError UncaughtThrowError FiberError IOError EOFError IndexError KeyError
            StopIteration LocalJumpError NameError NoMethodError RangeError FloatDomainError RegexpError
            RuntimeError FrozenError SystemCallError ThreadError TypeError ZeroDivisionError SystemExit
            SystemStackError fatal Warning buffer OpenSSL SSL SSLError Errno Timeout Exception EOFError
            ECONNRESET ECONNREFUSED ETIMEDOUT EINVAL ESHUTDOWN EHOSTDOWN EHOSTUNREACH EISCONN
            ECONNABORTED ENETRESET ENETUNREACH Net HTTPBadResponse HTTPHeaderSyntaxError ProtocolError
            Faraday BadRequestError UnauthorizedError ForbiddenError ResourceNotFound ProxyAuthError
            ConflictError UnprocessableEntityError ClientError
          ].cs__freeze
          CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'.cs__freeze
          # This will generate different chars for each agent startup but will be constant
          # for current run and will make identical obfuscation to be compared if given type
          # is the same.
          CYPHER = CHARS.chars.shuffle.join

          class << self
            # Returns paths for known gems.
            #
            # @return [Array<Regexp>] known paths
            def known_gem_paths
              @_known_gem_paths ||= KNOWN_GEMS.map do |gem_name|
                to_regexp(File.join(
                              'gems', gem_name.tr('-', ''), 'lib'))
              end
            end

            # Returns known modules names.
            #
            # @return [Array<Regexp>] known modules
            def known_modules
              @_known_modules ||= KNOWN_ERRORS.map { |module_name| to_regexp(module_name) }
            end

            # Obfuscate a type and replace it with random characters.
            #
            # @param type [String] the StackFrame type to obfuscate
            # @return [String] obfuscated type
            def obfuscate_type type
              return unless type

              check = type.tr('[^0-9].-', '')
              type = cypher(type) unless match_known(known_gem_paths, check)
              type
            end

            # Obfuscate a type and replace it with random characters.
            #
            # @param exception_type [String] the exception type to obfuscate
            # @return [String] obfuscated exception type
            def obfuscate_exception_type exception_type
              return unless exception_type

              exceptions = exception_type.split('::').each do |exception|
                cypher(exception) unless match_known(known_modules, exception)
              end
              exceptions.join('::')
            end

            private

            # Transforms string to regexp
            #
            # @param string [String]
            def to_regexp string
              /#{ string }/
            end

            # Add cypher to path to make it obscure, but unique enough for
            # comparisons. Mutates original or duplicate if frozen string.
            #
            # @param string [String] string to be transformed.
            def cypher string
              string = string.dup if string.cs__frozen?
              string.to_s.tr!(CHARS, CYPHER)
            end

            # @param known [Array<Regexp>] Array of regexp to match againts
            # @param type [String] type to check
            def match_known known, type
              known.any? { |regexp| type =~ regexp }
            end
          end
        end
      end
    end
  end
end