84 lines
2.5 KiB
Ruby
84 lines
2.5 KiB
Ruby
|
# frozen_string_literal: true
|
||
|
|
||
|
module Pages
|
||
|
class ZipDirectoryService
|
||
|
InvalidArchiveError = Class.new(RuntimeError)
|
||
|
InvalidEntryError = Class.new(RuntimeError)
|
||
|
|
||
|
PUBLIC_DIR = 'public'
|
||
|
|
||
|
def initialize(input_dir)
|
||
|
@input_dir = File.realpath(input_dir)
|
||
|
@output_file = File.join(@input_dir, "@migrated.zip") # '@' to avoid any name collision with groups or projects
|
||
|
end
|
||
|
|
||
|
def execute
|
||
|
FileUtils.rm_f(@output_file)
|
||
|
|
||
|
count = 0
|
||
|
::Zip::File.open(@output_file, ::Zip::File::CREATE) do |zipfile|
|
||
|
write_entry(zipfile, PUBLIC_DIR)
|
||
|
count = zipfile.entries.count
|
||
|
end
|
||
|
|
||
|
[@output_file, count]
|
||
|
end
|
||
|
|
||
|
private
|
||
|
|
||
|
def write_entry(zipfile, zipfile_path)
|
||
|
disk_file_path = File.join(@input_dir, zipfile_path)
|
||
|
|
||
|
unless valid_path?(disk_file_path)
|
||
|
# archive without public directory is completelly unusable
|
||
|
raise InvalidArchiveError if zipfile_path == PUBLIC_DIR
|
||
|
|
||
|
# archive with invalid entry will just have this entry missing
|
||
|
raise InvalidEntryError
|
||
|
end
|
||
|
|
||
|
case File.lstat(disk_file_path).ftype
|
||
|
when 'directory'
|
||
|
recursively_zip_directory(zipfile, disk_file_path, zipfile_path)
|
||
|
when 'file', 'link'
|
||
|
zipfile.add(zipfile_path, disk_file_path)
|
||
|
else
|
||
|
raise InvalidEntryError
|
||
|
end
|
||
|
rescue InvalidEntryError => e
|
||
|
Gitlab::ErrorTracking.track_exception(e, input_dir: @input_dir, disk_file_path: disk_file_path)
|
||
|
end
|
||
|
|
||
|
def recursively_zip_directory(zipfile, disk_file_path, zipfile_path)
|
||
|
zipfile.mkdir(zipfile_path)
|
||
|
|
||
|
entries = Dir.entries(disk_file_path) - %w[. ..]
|
||
|
entries = entries.map { |entry| File.join(zipfile_path, entry) }
|
||
|
|
||
|
write_entries(zipfile, entries)
|
||
|
end
|
||
|
|
||
|
def write_entries(zipfile, entries)
|
||
|
entries.each do |zipfile_path|
|
||
|
write_entry(zipfile, zipfile_path)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# that should never happen, but we want to be safer
|
||
|
# in theory without this we would allow to use symlinks
|
||
|
# to pack any directory on disk
|
||
|
# it isn't possible because SafeZip doesn't extract such archives
|
||
|
def valid_path?(disk_file_path)
|
||
|
realpath = File.realpath(disk_file_path)
|
||
|
|
||
|
realpath == File.join(@input_dir, PUBLIC_DIR) ||
|
||
|
realpath.start_with?(File.join(@input_dir, PUBLIC_DIR + "/"))
|
||
|
# happens if target of symlink isn't there
|
||
|
rescue => e
|
||
|
Gitlab::ErrorTracking.track_exception(e, input_dir: @input_dir, disk_file_path: disk_file_path)
|
||
|
|
||
|
false
|
||
|
end
|
||
|
end
|
||
|
end
|