# frozen_string_literal: true

module Import
  class ValidateRemoteGitEndpointService
    # Validates if the remote endpoint is a valid GIT repository
    # Only smart protocol is supported
    # Validation rules are taken from https://git-scm.com/docs/http-protocol#_smart_clients

    GIT_SERVICE_NAME = "git-upload-pack"
    GIT_EXPECTED_FIRST_PACKET_LINE = "# service=#{GIT_SERVICE_NAME}"
    GIT_BODY_MESSAGE_REGEXP = /^[0-9a-f]{4}#{GIT_EXPECTED_FIRST_PACKET_LINE}/.freeze
    # https://github.com/git/git/blob/master/Documentation/technical/protocol-common.txt#L56-L59
    GIT_PROTOCOL_PKT_LEN = 4
    GIT_MINIMUM_RESPONSE_LENGTH = GIT_PROTOCOL_PKT_LEN + GIT_EXPECTED_FIRST_PACKET_LINE.length
    EXPECTED_CONTENT_TYPE = "application/x-#{GIT_SERVICE_NAME}-advertisement"

    def initialize(params)
      @params = params
    end

    def execute
      uri = Gitlab::Utils.parse_url(@params[:url])

      return ServiceResponse.error(message: "#{@params[:url]} is not a valid URL") unless uri

      return ServiceResponse.success if uri.scheme == 'git'

      uri.fragment = nil
      url = Gitlab::Utils.append_path(uri.to_s, "/info/refs?service=#{GIT_SERVICE_NAME}")

      response_body = ''
      result = nil
      Gitlab::HTTP.try_get(url, stream_body: true, follow_redirects: false, basic_auth: auth) do |fragment|
        response_body += fragment
        next if response_body.length < GIT_MINIMUM_RESPONSE_LENGTH

        result = if status_code_is_valid(fragment) && content_type_is_valid(fragment) && response_body_is_valid(response_body)
                   :success
                 else
                   :error
                 end

        # We are interested only in the first chunks of the response
        # So we're using stream_body: true and breaking when receive enough body
        break
      end

      if result == :success
        ServiceResponse.success
      else
        ServiceResponse.error(message: "#{uri} is not a valid HTTP Git repository")
      end
    end

    private

    def auth
      unless @params[:user].to_s.blank?
        {
          username: @params[:user],
          password: @params[:password]
        }
      end
    end

    def status_code_is_valid(fragment)
      fragment.http_response.code == '200'
    end

    def content_type_is_valid(fragment)
      fragment.http_response['content-type'] == EXPECTED_CONTENT_TYPE
    end

    def response_body_is_valid(response_body)
      response_body.match?(GIT_BODY_MESSAGE_REGEXP)
    end
  end
end
