Welcome to mirror list, hosted at ThFree Co, Russian Federation.

file_decompression_service_spec.rb « bulk_imports « services « spec - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: e6d919c34993bd82209c63881ef2acfb2011dddd (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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe BulkImports::FileDecompressionService, feature_category: :importers do
  using RSpec::Parameterized::TableSyntax

  let_it_be(:tmpdir) { Dir.mktmpdir }
  let_it_be(:ndjson_filename) { 'labels.ndjson' }
  let_it_be(:ndjson_filepath) { File.join(tmpdir, ndjson_filename) }
  let_it_be(:gz_filename) { "#{ndjson_filename}.gz" }
  let_it_be(:gz_filepath) { "spec/fixtures/bulk_imports/gz/#{gz_filename}" }

  before do
    FileUtils.copy_file(gz_filepath, File.join(tmpdir, gz_filename))
    FileUtils.remove_entry(ndjson_filepath) if File.exist?(ndjson_filepath)
  end

  after(:all) do
    FileUtils.remove_entry(tmpdir)
  end

  subject { described_class.new(tmpdir: tmpdir, filename: gz_filename) }

  describe '#execute' do
    it 'decompresses specified file' do
      subject.execute

      expect(File.exist?(File.join(tmpdir, ndjson_filename))).to eq(true)
      expect(File.open(ndjson_filepath, &:readline)).to include('title', 'description')
    end

    context 'when validate_import_decompressed_archive_size feature flag is enabled' do
      before do
        stub_feature_flags(validate_import_decompressed_archive_size: true)
      end

      it 'performs decompressed file size validation' do
        expect_next_instance_of(Gitlab::ImportExport::DecompressedArchiveSizeValidator) do |validator|
          expect(validator).to receive(:valid?).and_return(true)
        end

        subject.execute
      end
    end

    context 'when validate_import_decompressed_archive_size feature flag is disabled' do
      before do
        stub_feature_flags(validate_import_decompressed_archive_size: false)
      end

      it 'does not perform decompressed file size validation' do
        expect(Gitlab::ImportExport::DecompressedArchiveSizeValidator).not_to receive(:new)

        subject.execute
      end
    end

    context 'when dir is not in tmpdir' do
      subject { described_class.new(tmpdir: '/etc', filename: 'filename') }

      it 'raises an error' do
        expect { subject.execute }.to raise_error(StandardError, 'path /etc is not allowed')
      end
    end

    context 'when path is being traversed' do
      subject { described_class.new(tmpdir: File.join(Dir.mktmpdir, 'test', '..'), filename: 'filename') }

      it 'raises an error' do
        expect { subject.execute }.to raise_error(Gitlab::PathTraversal::PathTraversalAttackError, 'Invalid path')
      end
    end

    shared_examples 'raises an error and removes the file' do |error_message:|
      specify do
        expect { subject.execute }
          .to raise_error(BulkImports::FileDecompressionService::ServiceError, error_message)
        expect(File).not_to exist(file)
      end
    end

    shared_context 'when compressed file' do
      let_it_be(:file) { File.join(tmpdir, 'file.gz') }

      subject { described_class.new(tmpdir: tmpdir, filename: 'file.gz') }

      before do
        FileUtils.send(link_method, File.join(tmpdir, gz_filename), file)
      end
    end

    shared_context 'when decompressed file' do
      let_it_be(:file) { File.join(tmpdir, 'file.txt') }

      subject { described_class.new(tmpdir: tmpdir, filename: gz_filename) }

      before do
        original_file = File.join(tmpdir, 'original_file.txt')
        FileUtils.touch(original_file)
        FileUtils.send(link_method, original_file, file)

        subject.instance_variable_set(:@decompressed_filepath, file)
      end
    end

    context 'when compressed file is a symlink' do
      let(:link_method) { :symlink }

      include_context 'when compressed file'

      include_examples 'raises an error and removes the file', error_message: 'File decompression error'
    end

    context 'when compressed file shares multiple hard links' do
      let(:link_method) { :link }

      include_context 'when compressed file'

      include_examples 'raises an error and removes the file', error_message: 'File decompression error'
    end

    context 'when decompressed file is a symlink' do
      let(:link_method) { :symlink }

      include_context 'when decompressed file'

      include_examples 'raises an error and removes the file', error_message: 'Invalid file'
    end

    context 'when decompressed file shares multiple hard links' do
      let(:link_method) { :link }

      include_context 'when decompressed file'

      include_examples 'raises an error and removes the file', error_message: 'Invalid file'
    end
  end
end