Commit 12b637e6 authored by Vladimir Shushlin's avatar Vladimir Shushlin

Handle the case when input_dir is absent

It is possible that not only the `public` directory is absent,
but the whole path isn't valid, this commit handles that
parent 94750f41
...@@ -8,20 +8,24 @@ module Pages ...@@ -8,20 +8,24 @@ module Pages
PUBLIC_DIR = 'public' PUBLIC_DIR = 'public'
def initialize(input_dir) def initialize(input_dir)
@input_dir = File.realpath(input_dir) @input_dir = input_dir
@output_file = File.join(@input_dir, "@migrated.zip") # '@' to avoid any name collision with groups or projects
end end
def execute def execute
FileUtils.rm_f(@output_file) raise InvalidArchiveError unless valid_work_directory?
@input_dir = File.realpath(@input_dir)
output_file = File.join(@input_dir, "@migrated.zip") # '@' to avoid any name collision with groups or projects
FileUtils.rm_f(output_file)
count = 0 count = 0
::Zip::File.open(@output_file, ::Zip::File::CREATE) do |zipfile| ::Zip::File.open(output_file, ::Zip::File::CREATE) do |zipfile|
write_entry(zipfile, PUBLIC_DIR) write_entry(zipfile, PUBLIC_DIR)
count = zipfile.entries.count count = zipfile.entries.count
end end
[@output_file, count] [output_file, count]
end end
private private
...@@ -79,5 +83,13 @@ module Pages ...@@ -79,5 +83,13 @@ module Pages
false false
end end
def valid_work_directory?
Dir.exist?(@input_dir)
rescue => e
Gitlab::ErrorTracking.track_exception(e, input_dir: @input_dir)
false
end
end end
end end
...@@ -3,207 +3,215 @@ ...@@ -3,207 +3,215 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Pages::ZipDirectoryService do RSpec.describe Pages::ZipDirectoryService do
around do |example| it 'raises error if project pages dir does not exist' do
Dir.mktmpdir do |dir| expect do
@work_dir = dir described_class.new("/tmp/not/existing/dir").execute
example.run end.to raise_error(described_class::InvalidArchiveError)
end
end end
let(:result) do context 'when work dir exists' do
described_class.new(@work_dir).execute around do |example|
end Dir.mktmpdir do |dir|
@work_dir = dir
example.run
end
end
let(:archive) { result.first } let(:result) do
let(:entries_count) { result.second } described_class.new(@work_dir).execute
end
it 'raises error if there is no public directory' do let(:archive) { result.first }
expect { archive }.to raise_error(described_class::InvalidArchiveError) let(:entries_count) { result.second }
end
it 'raises error if public directory is a symlink' do it 'raises error if there is no public directory' do
create_dir('target') expect { archive }.to raise_error(described_class::InvalidArchiveError)
create_file('./target/index.html', 'hello') end
create_link("public", "./target")
expect { archive }.to raise_error(described_class::InvalidArchiveError) it 'raises error if public directory is a symlink' do
end create_dir('target')
create_file('./target/index.html', 'hello')
create_link("public", "./target")
context 'when there is a public directory' do expect { archive }.to raise_error(described_class::InvalidArchiveError)
before do
create_dir('public')
end end
it 'creates the file next the public directory' do context 'when there is a public directory' do
expect(archive).to eq(File.join(@work_dir, "@migrated.zip")) before do
end create_dir('public')
end
it 'includes public directory' do it 'creates the file next the public directory' do
with_zip_file do |zip_file| expect(archive).to eq(File.join(@work_dir, "@migrated.zip"))
entry = zip_file.get_entry("public/")
expect(entry.ftype).to eq(:directory)
end end
end
it 'returns number of entries' do it 'includes public directory' do
create_file("public/index.html", "hello") with_zip_file do |zip_file|
create_link("public/link.html", "./index.html") entry = zip_file.get_entry("public/")
expect(entries_count).to eq(3) # + 'public' directory expect(entry.ftype).to eq(:directory)
end end
end
it 'removes the old file if it exists' do it 'returns number of entries' do
# simulate the old run create_file("public/index.html", "hello")
described_class.new(@work_dir).execute create_link("public/link.html", "./index.html")
expect(entries_count).to eq(3) # + 'public' directory
end
it 'removes the old file if it exists' do
# simulate the old run
described_class.new(@work_dir).execute
with_zip_file do |zip_file| with_zip_file do |zip_file|
expect(zip_file.entries.count).to eq(1) expect(zip_file.entries.count).to eq(1)
end
end end
end
it 'ignores other top level files and directories' do it 'ignores other top level files and directories' do
create_file("top_level.html", "hello") create_file("top_level.html", "hello")
create_dir("public2") create_dir("public2")
with_zip_file do |zip_file| with_zip_file do |zip_file|
expect { zip_file.get_entry("top_level.html") }.to raise_error(Errno::ENOENT) expect { zip_file.get_entry("top_level.html") }.to raise_error(Errno::ENOENT)
expect { zip_file.get_entry("public2/") }.to raise_error(Errno::ENOENT) expect { zip_file.get_entry("public2/") }.to raise_error(Errno::ENOENT)
end
end end
end
it 'includes index.html file' do it 'includes index.html file' do
create_file("public/index.html", "Hello!") create_file("public/index.html", "Hello!")
with_zip_file do |zip_file| with_zip_file do |zip_file|
entry = zip_file.get_entry("public/index.html") entry = zip_file.get_entry("public/index.html")
expect(zip_file.read(entry)).to eq("Hello!") expect(zip_file.read(entry)).to eq("Hello!")
end
end end
end
it 'includes hidden file' do it 'includes hidden file' do
create_file("public/.hidden.html", "Hello!") create_file("public/.hidden.html", "Hello!")
with_zip_file do |zip_file| with_zip_file do |zip_file|
entry = zip_file.get_entry("public/.hidden.html") entry = zip_file.get_entry("public/.hidden.html")
expect(zip_file.read(entry)).to eq("Hello!") expect(zip_file.read(entry)).to eq("Hello!")
end
end end
end
it 'includes nested directories and files' do it 'includes nested directories and files' do
create_dir("public/nested") create_dir("public/nested")
create_dir("public/nested/nested2") create_dir("public/nested/nested2")
create_file("public/nested/nested2/nested.html", "Hello nested") create_file("public/nested/nested2/nested.html", "Hello nested")
with_zip_file do |zip_file| with_zip_file do |zip_file|
entry = zip_file.get_entry("public/nested") entry = zip_file.get_entry("public/nested")
expect(entry.ftype).to eq(:directory) expect(entry.ftype).to eq(:directory)
entry = zip_file.get_entry("public/nested/nested2") entry = zip_file.get_entry("public/nested/nested2")
expect(entry.ftype).to eq(:directory) expect(entry.ftype).to eq(:directory)
entry = zip_file.get_entry("public/nested/nested2/nested.html") entry = zip_file.get_entry("public/nested/nested2/nested.html")
expect(zip_file.read(entry)).to eq("Hello nested") expect(zip_file.read(entry)).to eq("Hello nested")
end
end end
end
it 'adds a valid symlink' do it 'adds a valid symlink' do
create_file("public/target.html", "hello") create_file("public/target.html", "hello")
create_link("public/link.html", "./target.html") create_link("public/link.html", "./target.html")
with_zip_file do |zip_file| with_zip_file do |zip_file|
entry = zip_file.get_entry("public/link.html") entry = zip_file.get_entry("public/link.html")
expect(entry.ftype).to eq(:symlink) expect(entry.ftype).to eq(:symlink)
expect(zip_file.read(entry)).to eq("./target.html") expect(zip_file.read(entry)).to eq("./target.html")
end
end end
end
it 'ignores the symlink pointing outside of public directory' do it 'ignores the symlink pointing outside of public directory' do
create_file("target.html", "hello") create_file("target.html", "hello")
create_link("public/link.html", "../target.html") create_link("public/link.html", "../target.html")
with_zip_file do |zip_file| with_zip_file do |zip_file|
expect { zip_file.get_entry("public/link.html") }.to raise_error(Errno::ENOENT) expect { zip_file.get_entry("public/link.html") }.to raise_error(Errno::ENOENT)
end
end end
end
it 'ignores the symlink if target is absent' do it 'ignores the symlink if target is absent' do
create_link("public/link.html", "./target.html") create_link("public/link.html", "./target.html")
with_zip_file do |zip_file| with_zip_file do |zip_file|
expect { zip_file.get_entry("public/link.html") }.to raise_error(Errno::ENOENT) expect { zip_file.get_entry("public/link.html") }.to raise_error(Errno::ENOENT)
end
end end
end
it 'ignores symlink if is absolute and points to outside of directory' do it 'ignores symlink if is absolute and points to outside of directory' do
target = File.join(@work_dir, "target") target = File.join(@work_dir, "target")
FileUtils.touch(target) FileUtils.touch(target)
create_link("public/link.html", target) create_link("public/link.html", target)
with_zip_file do |zip_file| with_zip_file do |zip_file|
expect { zip_file.get_entry("public/link.html") }.to raise_error(Errno::ENOENT) expect { zip_file.get_entry("public/link.html") }.to raise_error(Errno::ENOENT)
end
end end
end
it "includes raw symlink if it's target is a valid directory" do it "includes raw symlink if it's target is a valid directory" do
create_dir("public/target") create_dir("public/target")
create_file("public/target/index.html", "hello") create_file("public/target/index.html", "hello")
create_link("public/link", "./target") create_link("public/link", "./target")
with_zip_file do |zip_file| with_zip_file do |zip_file|
expect(zip_file.entries.count).to eq(4) # /public and 3 created above expect(zip_file.entries.count).to eq(4) # /public and 3 created above
entry = zip_file.get_entry("public/link") entry = zip_file.get_entry("public/link")
expect(entry.ftype).to eq(:symlink) expect(entry.ftype).to eq(:symlink)
expect(zip_file.read(entry)).to eq("./target") expect(zip_file.read(entry)).to eq("./target")
end
end end
end end
end
context "validating fixtures pages archives" do context "validating fixtures pages archives" do
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
where(:fixture_path) do where(:fixture_path) do
["spec/fixtures/pages.zip", "spec/fixtures/pages_non_writeable.zip"] ["spec/fixtures/pages.zip", "spec/fixtures/pages_non_writeable.zip"]
end end
with_them do with_them do
let(:full_fixture_path) { Rails.root.join(fixture_path) } let(:full_fixture_path) { Rails.root.join(fixture_path) }
it 'a created archives contains exactly the same entries' do it 'a created archives contains exactly the same entries' do
SafeZip::Extract.new(full_fixture_path).extract(directories: ['public'], to: @work_dir) SafeZip::Extract.new(full_fixture_path).extract(directories: ['public'], to: @work_dir)
with_zip_file do |created_archive| with_zip_file do |created_archive|
Zip::File.open(full_fixture_path) do |original_archive| Zip::File.open(full_fixture_path) do |original_archive|
original_archive.entries do |original_entry| original_archive.entries do |original_entry|
created_entry = created_archive.get_entry(original_entry.name) created_entry = created_archive.get_entry(original_entry.name)
expect(created_entry.name).to eq(original_entry.name) expect(created_entry.name).to eq(original_entry.name)
expect(created_entry.ftype).to eq(original_entry.ftype) expect(created_entry.ftype).to eq(original_entry.ftype)
expect(created_archive.read(created_entry)).to eq(original_archive.read(original_entry)) expect(created_archive.read(created_entry)).to eq(original_archive.read(original_entry))
end
end end
end end
end end
end end
end end
end
def create_file(name, content) def create_file(name, content)
File.open(File.join(@work_dir, name), "w") do |f| File.open(File.join(@work_dir, name), "w") do |f|
f.write(content) f.write(content)
end
end end
end
def create_dir(dir) def create_dir(dir)
Dir.mkdir(File.join(@work_dir, dir)) Dir.mkdir(File.join(@work_dir, dir))
end end
def create_link(new_name, target) def create_link(new_name, target)
File.symlink(target, File.join(@work_dir, new_name)) File.symlink(target, File.join(@work_dir, new_name))
end end
def with_zip_file def with_zip_file
Zip::File.open(archive) do |zip_file| Zip::File.open(archive) do |zip_file|
yield zip_file yield zip_file
end
end end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment