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
|