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

if defined?(Sinatra) && defined?(Sinatra::Base)
  module Sinatra
    # Our patch into the Sinatra::Base Class, allowing for the inventory of the
    # routes called by the application
    class Base
      alias_method :cs__patched_call!, :call!

      # publicly available method for Sinatra::Base things -- unfortunately,
      # getting the routes appear to require a lookup every time
      def call! *args
        cs__patched_map_route(*args)
        cs__patched_call!(*args)
      end

      private

      # Use logic copied from Sinatra::Base#process_route to determine which
      # pattern matches the request being invoked by Sinatra::Base#call!
      #
      # @param args [Array<Object>] we rely on the @settings object in
      #   Sinatra::Base and the env variable passed in as args[0]
      def cs__patched_map_route *args
        context = Contrast::Agent::REQUEST_TRACKER.current
        return unless context

        env = args[0]
        return unless env

        # There isn't a Request object in the Sinatra::Base yet - it's made
        # during the #call! method, which we're patching - so we need to
        # access the env
        method = env[Rack::REQUEST_METHOD]
        route = env[Rack::PATH_INFO]
        route = route.to_s

        # get all potential routes for this request type
        routes = settings.routes[method]
        return unless routes

        # Do some cleanup that matches Sinatra::Base#process_route
        route = '/' if route.empty? && !settings.empty_path_info?
        route = route[0..-2] if !settings.strict_paths? && route != '/' && route.end_with?('/')

        # Find the route that matches this request. Since we're using
        # settings, we should resolve in the same precedence as Sinatra
        routes.each do |pattern, _, _| # Mustermann::Sinatra
          next unless pattern.params(route)

          dtm = Contrast::Utils::PathUtil.get_sinatra_route(cs__class, method, pattern)
          context.append_route_coverage(dtm)
          break
        end
      end
    end
  end
end