summaryrefslogtreecommitdiff
path: root/lib/gitlab/import_export/decompressed_archive_size_validator.rb
blob: 219821a7150f4939832cb2ebc05d8f3300129779 (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
85
86
87
88
89
90
# frozen_string_literal: true

require 'zlib'

module Gitlab
  module ImportExport
    class DecompressedArchiveSizeValidator
      include Gitlab::Utils::StrongMemoize

      DEFAULT_MAX_BYTES = 10.gigabytes.freeze
      CHUNK_SIZE = 4096.freeze

      attr_reader :error

      def initialize(archive_path:, max_bytes: self.class.max_bytes)
        @archive_path = archive_path
        @max_bytes = max_bytes
        @bytes_read = 0
        @total_reads = 0
        @denominator = 5
        @error = nil
      end

      def valid?
        strong_memoize(:valid) do
          validate
        end
      end

      def self.max_bytes
        DEFAULT_MAX_BYTES
      end

      def archive_file
        @archive_file ||= File.open(@archive_path)
      end

      private

      def validate
        until archive_file.eof?
          compressed_chunk = archive_file.read(CHUNK_SIZE)

          inflate_stream.inflate(compressed_chunk) do |chunk|
            @bytes_read += chunk.size
            @total_reads += 1
          end

          # Start garbage collection every 5 reads in order
          # to prevent memory bloat during archive decompression
          GC.start if gc_start?

          if @bytes_read > @max_bytes
            @error = error_message

            return false
          end
        end

        true
      rescue => e
        @error = error_message

        Gitlab::ErrorTracking.track_exception(e)

        Gitlab::Import::Logger.info(
          message: @error,
          error: e.message
        )

        false
      ensure
        inflate_stream.close
        archive_file.close
      end

      def inflate_stream
        @inflate_stream ||= Zlib::Inflate.new(Zlib::MAX_WBITS + 32)
      end

      def gc_start?
        @total_reads % @denominator == 0
      end

      def error_message
        _('Decompressed archive size validation failed.')
      end
    end
  end
end