summaryrefslogtreecommitdiff
path: root/app/services/projects/lfs_pointers/lfs_download_service.rb
blob: b5128443435edd5af210d6b931d80966d4ce0b08 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# frozen_string_literal: true

# This service downloads and links lfs objects from a remote URL
module Projects
  module LfsPointers
    class LfsDownloadService < BaseService
      VALID_PROTOCOLS = %w[http https].freeze

      # rubocop: disable CodeReuse/ActiveRecord
      def execute(oid, url)
        return unless project&.lfs_enabled? && oid.present? && url.present?

        return if LfsObject.exists?(oid: oid)

        sanitized_uri = sanitize_url!(url)

        with_tmp_file(oid) do |file|
          download_and_save_file(file, sanitized_uri)
          lfs_object = LfsObject.new(oid: oid, size: file.size, file: file)

          project.all_lfs_objects << lfs_object
        end
      rescue Gitlab::UrlBlocker::BlockedUrlError => e
        Rails.logger.error("LFS file with oid #{oid} couldn't be downloaded: #{e.message}")
      rescue StandardError => e
        Rails.logger.error("LFS file with oid #{oid} couldn't be downloaded from #{sanitized_uri.sanitized_url}: #{e.message}")
      end
      # rubocop: enable CodeReuse/ActiveRecord

      private

      def sanitize_url!(url)
        Gitlab::UrlSanitizer.new(url).tap do |sanitized_uri|
          # Just validate that HTTP/HTTPS protocols are used. The
          # subsequent Gitlab::HTTP.get call will do network checks
          # based on the settings.
          Gitlab::UrlBlocker.validate!(sanitized_uri.sanitized_url,
                                       protocols: VALID_PROTOCOLS)
        end
      end

      def download_and_save_file(file, sanitized_uri)
        response = Gitlab::HTTP.get(sanitized_uri.sanitized_url, headers(sanitized_uri)) do |fragment|
          file.write(fragment)
        end

        raise StandardError, "Received error code #{response.code}" unless response.success?
      end

      def headers(sanitized_uri)
        query_options.tap do |headers|
          credentials = sanitized_uri.credentials

          if credentials[:user].present? || credentials[:password].present?
            # Using authentication headers in the request
            headers[:http_basic_authentication] = [credentials[:user], credentials[:password]]
          end
        end
      end

      def query_options
        { stream_body: true }
      end

      def with_tmp_file(oid)
        create_tmp_storage_dir

        File.open(File.join(tmp_storage_dir, oid), 'wb') { |file| yield file }
      end

      def create_tmp_storage_dir
        FileUtils.makedirs(tmp_storage_dir) unless Dir.exist?(tmp_storage_dir)
      end

      def tmp_storage_dir
        @tmp_storage_dir ||= File.join(storage_dir, 'tmp', 'download')
      end

      def storage_dir
        @storage_dir ||= Gitlab.config.lfs.storage_path
      end
    end
  end
end