discourse/spec/lib/compression/engine_spec.rb

186 lines
6.9 KiB
Ruby

# frozen_string_literal: true
RSpec.describe Compression::Engine do
let(:available_size) { SiteSetting.decompressed_theme_max_file_size_mb }
let(:folder_name) { "test" }
let(:temp_folder) do
path = "#{Pathname.new(Dir.tmpdir).realpath}/#{SecureRandom.hex}"
FileUtils.mkdir(path)
path
end
before do
Dir.chdir(temp_folder) do
FileUtils.mkdir_p("#{folder_name}/a")
File.write("#{folder_name}/hello.txt", "hello world")
File.write("#{folder_name}/a/inner", "hello world inner")
end
end
after { FileUtils.rm_rf(temp_folder) }
it "raises an exception when the file is not supported" do
unknown_extension = "a_file.crazyext"
expect {
described_class.engine_for(unknown_extension)
}.to raise_error Compression::Engine::UnsupportedFileExtension
end
describe "compressing and decompressing files" do
before do
Dir.chdir(temp_folder) do
@compressed_path =
Compression::Engine.engine_for("#{folder_name}#{extension}").compress(
temp_folder,
folder_name,
)
FileUtils.rm_rf("#{folder_name}/")
end
end
context "when working with zip files" do
let(:extension) { ".zip" }
it "decompresses the folder and inspects files correctly" do
engine = described_class.engine_for(@compressed_path)
extract_location = "#{temp_folder}/extract_location"
FileUtils.mkdir(extract_location)
engine.decompress(extract_location, "#{temp_folder}/#{folder_name}.zip", available_size)
expect(read_file("extract_location/hello.txt")).to eq("hello world")
expect(read_file("extract_location/a/inner")).to eq("hello world inner")
end
it "doesn't allow files to be extracted outside the target directory" do
FileUtils.rm_rf(temp_folder)
FileUtils.mkdir(temp_folder)
zip_file = "#{temp_folder}/theme.zip"
Zip::File.open(zip_file, create: true) do |zipfile|
zipfile.get_output_stream("child-file") { |f| f.puts("child file") }
zipfile.get_output_stream("../escape-decompression-folder.txt") do |f|
f.puts("file that attempts to escape the decompression destination directory")
end
zipfile.mkdir("child-dir")
zipfile.get_output_stream("child-dir/grandchild-file") { |f| f.puts("grandchild file") }
end
extract_location = "#{temp_folder}/extract_location"
FileUtils.mkdir(extract_location)
engine = described_class.engine_for(zip_file)
engine.decompress(extract_location, zip_file, available_size)
Dir.chdir(temp_folder) do
expect(Dir.glob("**/*")).to contain_exactly(
"extract_location",
"extract_location/child-file",
"extract_location/child-dir",
"extract_location/child-dir/grandchild-file",
"theme.zip",
)
end
end
it "decompresses into symlinked directory" do
real_location = "#{temp_folder}/extract_location"
extract_location = "#{temp_folder}/is/symlinked"
FileUtils.mkdir(real_location)
FileUtils.mkdir_p(extract_location)
extract_location = "#{extract_location}/extract_location"
FileUtils.symlink(real_location, extract_location)
engine = described_class.engine_for(@compressed_path)
engine.decompress(extract_location, "#{temp_folder}/#{folder_name}.zip", available_size)
expect(File.realpath(extract_location)).to eq(real_location)
expect(read_file("is/symlinked/extract_location/hello.txt")).to eq("hello world")
expect(read_file("is/symlinked/extract_location/a/inner")).to eq("hello world inner")
end
end
context "when working with .tar.gz files" do
let(:extension) { ".tar.gz" }
it "decompresses the folder and inspects files correctly" do
engine = described_class.engine_for(@compressed_path)
engine.decompress(temp_folder, "#{temp_folder}/#{folder_name}.tar.gz", available_size)
expect(read_file("test/hello.txt")).to eq("hello world")
expect(read_file("test/a/inner")).to eq("hello world inner")
end
it "doesn't allow files to be extracted outside the target directory" do
FileUtils.rm_rf(temp_folder)
FileUtils.mkdir(temp_folder)
tar_file = "#{temp_folder}/theme.tar"
File.open(tar_file, "wb") do |file|
Gem::Package::TarWriter.new(file) do |tar|
tar.add_file("child-file", 644) { |tf| tf.write("child file") }
tar.add_file("../escape-extraction-folder", 644) do |tf|
tf.write("file that attempts to escape the decompression destination directory")
end
tar.mkdir("child-dir", 755)
tar.add_file("child-dir/grandchild-file", 644) { |tf| tf.write("grandchild file") }
end
end
tar_gz_file = "#{temp_folder}/theme.tar.gz"
Zlib::GzipWriter.open(tar_gz_file) do |gz|
gz.orig_name = tar_file
gz.write(File.binread(tar_file))
end
FileUtils.rm(tar_file)
extract_location = "#{temp_folder}/extract_location"
FileUtils.mkdir(extract_location)
engine = described_class.engine_for(tar_gz_file)
engine.decompress(extract_location, tar_gz_file, available_size)
Dir.chdir(temp_folder) do
expect(Dir.glob("**/*")).to contain_exactly(
"extract_location",
"extract_location/child-file",
"extract_location/child-dir",
"extract_location/child-dir/grandchild-file",
)
end
end
it "decompresses into symlinked directory" do
real_location = "#{temp_folder}/extract_location"
extract_location = "#{temp_folder}/is/symlinked"
FileUtils.mkdir(real_location)
FileUtils.mkdir_p(extract_location)
extract_location = "#{extract_location}/extract_location"
FileUtils.symlink(real_location, extract_location)
engine = described_class.engine_for(@compressed_path)
engine.decompress(extract_location, "#{temp_folder}/#{folder_name}.tar.gz", available_size)
expect(File.realpath(extract_location)).to eq(real_location)
expect(read_file("is/symlinked/extract_location/test/hello.txt")).to eq("hello world")
expect(read_file("is/symlinked/extract_location/test/a/inner")).to eq("hello world inner")
end
end
context "when working with .tar files" do
let(:extension) { ".tar" }
it "decompress the folder and inspect files correctly" do
engine = described_class.engine_for(@compressed_path)
engine.decompress(temp_folder, "#{temp_folder}/#{folder_name}.tar", available_size)
expect(read_file("test/hello.txt")).to eq("hello world")
expect(read_file("test/a/inner")).to eq("hello world inner")
end
end
end
def read_file(relative_path)
File.read("#{temp_folder}/#{relative_path}")
end
end