2019-02-02 18:00:53 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
module SafeZip
|
2023-05-27 22:25:52 +05:30
|
|
|
# SafeZip::Extract provides a safe interface
|
|
|
|
# to extract specific directories or files within a `zip` archive.
|
|
|
|
#
|
|
|
|
# @example Extract directories to destination
|
|
|
|
# SafeZip::Extract.new(archive_file).extract(directories: ['app/', 'test/'], to: destination_path)
|
|
|
|
# @example Extract files to destination
|
|
|
|
# SafeZip::Extract.new(archive_file).extract(files: ['index.html', 'app/index.js'], to: destination_path)
|
2019-02-02 18:00:53 +05:30
|
|
|
class Extract
|
|
|
|
Error = Class.new(StandardError)
|
|
|
|
PermissionDeniedError = Class.new(Error)
|
|
|
|
SymlinkSourceDoesNotExistError = Class.new(Error)
|
|
|
|
UnsupportedEntryError = Class.new(Error)
|
2023-02-26 17:17:37 +05:30
|
|
|
EntrySizeError = Class.new(Error)
|
2019-02-02 18:00:53 +05:30
|
|
|
AlreadyExistsError = Class.new(Error)
|
|
|
|
NoMatchingError = Class.new(Error)
|
|
|
|
ExtractError = Class.new(Error)
|
|
|
|
|
|
|
|
attr_reader :archive_path
|
|
|
|
|
|
|
|
def initialize(archive_file)
|
|
|
|
@archive_path = archive_file
|
|
|
|
end
|
|
|
|
|
2023-05-27 22:25:52 +05:30
|
|
|
# extract given files or directories from the archive into the destination path
|
|
|
|
#
|
|
|
|
# @param [Hash] opts the options for extraction.
|
|
|
|
# @option opts [Array<String] :files list of files to be extracted
|
|
|
|
# @option opts [Array<String] :directories list of directories to be extracted
|
|
|
|
# @option opts [String] :to destination path
|
|
|
|
#
|
|
|
|
# @raise [PermissionDeniedError]
|
|
|
|
# @raise [SymlinkSourceDoesNotExistError]
|
|
|
|
# @raise [UnsupportedEntryError]
|
|
|
|
# @raise [EntrySizeError]
|
|
|
|
# @raise [AlreadyExistsError]
|
|
|
|
# @raise [NoMatchingError]
|
|
|
|
# @raise [ExtractError]
|
2019-02-02 18:00:53 +05:30
|
|
|
def extract(opts = {})
|
|
|
|
params = SafeZip::ExtractParams.new(**opts)
|
|
|
|
|
2021-01-03 14:25:43 +05:30
|
|
|
extract_with_ruby_zip(params)
|
2019-02-02 18:00:53 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def extract_with_ruby_zip(params)
|
2022-01-26 12:08:38 +05:30
|
|
|
::Zip::File.open(archive_path) do |zip_archive| # rubocop:disable Performance/Rubyzip
|
2019-02-02 18:00:53 +05:30
|
|
|
# Extract all files in the following order:
|
|
|
|
# 1. Directories first,
|
|
|
|
# 2. Files next,
|
|
|
|
# 3. Symlinks last (or anything else)
|
|
|
|
extracted = extract_all_entries(zip_archive, params,
|
|
|
|
zip_archive.lazy.select(&:directory?))
|
|
|
|
|
|
|
|
extracted += extract_all_entries(zip_archive, params,
|
|
|
|
zip_archive.lazy.select(&:file?))
|
|
|
|
|
|
|
|
extracted += extract_all_entries(zip_archive, params,
|
|
|
|
zip_archive.lazy.reject(&:directory?).reject(&:file?))
|
|
|
|
|
|
|
|
raise NoMatchingError, 'No entries extracted' unless extracted > 0
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def extract_all_entries(zip_archive, params, entries)
|
|
|
|
entries.count do |zip_entry|
|
|
|
|
SafeZip::Entry.new(zip_archive, zip_entry, params)
|
|
|
|
.extract
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|