Updating to version 3.x
frozen_string_literal: true
.
There are a few options whose defaults have changed:
-
Zip64 extensions support is turned on by default. This should not affect reading files at all, but if you really need to ensure that Zip64 extensions are not used (say, because you need to support a really old version of ZIP) then you can turn them off. See the Configuration section of the README for details of this.
-
Zip::File#restore_times
is now set totrue
by default. This means that when files are extracted from a ZIP archive, RubyZip will try to restore the timestamps to those stored within the archive. -
Zip::File#restore_permissions
is now set totrue
by default. This means that when files are extracted from a ZIP archive, RubyZip will try to restore the read, write and execute permissions to those stored within the archive.
In the next section we list the major API changes in version 3. To help you migrate we have done two things:
- Wherever possible we have made the methods listed below compatible with both the 2.x API and 3.x API.
-
File#extract
andEntry#extract
are notable exceptions. See their descriptions below for details.
-
- We have added configurable warnings to tell you where you are using version 2 API calls. Please note that this is as comprehensive as possible, but may not catch everything.
- Run your tests and satisfy yourself that everything is working as expected before going any further. (I know you know to do this, but this is a reminder for everyone else.)
- Update your dependencies to use version 2.4 of the Rubyzip gem (coming soon!).
- Run your tests - everything should still work as expected at this stage.
- Run your tests with
--enable-frozen-string-literal
set - everything should still work as expected at this stage. - Run your tests with the environment variable
RUBYZIP_V3_API_WARN
set. E.g.:You should see warnings where you need to make changes, and if you are using an old version of Ruby.$ RUBYZIP_V3_API_WARN=1 rake test
- Make the required changes so that the messages disappear, or are at least minimised. See the next section for the details of the changes.
- Update your dependencies to use version 3.0 of the Rubyzip gem (coming soon!).
- Run your tests and check everything still works.
There are a number of places where the API has changed between version 2.x and 3.x
Most changes are due to methods now using named parameters.
No changes in functionality, but now uses named parameters for readability:
- def initialize(path_or_io, create = false, buffer = false, options = {})
+ def initialize(path_or_io, create: false, buffer: false,
+ restore_ownership: DEFAULT_RESTORE_OPTIONS[:restore_ownership],
+ restore_permissions: DEFAULT_RESTORE_OPTIONS[:restore_permissions],
+ restore_times: DEFAULT_RESTORE_OPTIONS[:restore_times],
+ compression_level: ::Zip.default_compression)
In general, use of File::new
is discouraged; favour ::open
and ::open_buffer
if possible.
This method has been removed. Please use ::open_buffer
instead.
This is a new method. Use this to count the number of entries in an archive without reading in the whole central directory, or stepping through all the entries with InputStream
.
No changes in functionality, but now uses named parameters for readability:
- def open(file_name, create = false, options = {})
+ def open(file_name, create: false,
+ restore_ownership: DEFAULT_RESTORE_OPTIONS[:restore_ownership],
+ restore_permissions: DEFAULT_RESTORE_OPTIONS[:restore_permissions],
+ restore_times: DEFAULT_RESTORE_OPTIONS[:restore_times],
+ compression_level: ::Zip.default_compression)
No longer assumes that opening a buffer is to create a new archive in it. Also now uses named parameters for readability:
- def open_buffer(io, **options)
+ def open_buffer(io = ::StringIO.new, create: false,
+ restore_ownership: DEFAULT_RESTORE_OPTIONS[:restore_ownership],
+ restore_permissions: DEFAULT_RESTORE_OPTIONS[:restore_permissions],
+ restore_times: DEFAULT_RESTORE_OPTIONS[:restore_times],
+ compression_level: ::Zip.default_compression)
No changes in functionality, but now uses named parameters for readability:
- def split(zip_file_name, segment_size = MAX_SEGMENT_SIZE, delete_zip_file = true, partial_zip_file_name = nil)
+ def split(zip_file_name, segment_size: MAX_SEGMENT_SIZE, delete_original: true, partial_zip_file_name: nil)
Major Update!
This method now forces extraction into the current working directory unless this is overridden by supplying a different directory via destination_directory
.
- def extract(entry, dest_path, &block)
+ def extract(entry, entry_path = nil, destination_directory: '.', &block)
File#extract_v3
for the version 3.x API.
This effectively splits the final location of an extracted entry into two parts:
- the base directory (controlled by
destination_directory
and.
by default); and - the entry path (controlled by
entry_path
and the entry's name by default).
So, if the current working directory is /tmp
, then the following holds for various combinations of entry name and the parameters supplied to #extract
:
entry name | entry_path |
destination_directory |
resulting path |
---|---|---|---|
foo/bar.txt |
<not set> |
<not set> |
/tmp/foo/bar.txt |
foo/bar.txt |
<not set> |
/home/me/work |
/home/me/work/foo/bar.txt |
foo/bar.txt |
<not set> |
files |
/tmp/files/foo/bar.txt |
foo/bar.txt |
baz.txt |
<not set> |
/tmp/baz.txt |
foo/bar.txt |
baz.txt |
/home/me/work |
/home/me/work/baz.txt |
foo/bar.txt |
baz.txt |
files |
/tmp/files/baz.txt |
../bar.txt |
<not set> |
<not set> |
Extraction is skipped and a warning printed to stderr
|
../bar.txt |
<not set> |
/home/me/work |
Extraction is skipped and a warning printed to stderr
|
../bar.txt |
<not set> |
files |
Extraction is skipped and a warning printed to stderr
|
../bar.txt |
baz.txt |
<not set> |
/tmp/baz.txt |
../bar.txt |
baz.txt |
/home/me/work |
/home/me/work/baz.txt |
../bar.txt |
baz.txt |
files |
/tmp/files/baz.txt |
The rationale for this change is to mitigate against so called 'path traversal' hacks. For example, a Zip file may contain an entry called ../etc/passwd
in the hope that someone would unpack it in /tmp
with raised privileges - without path traversal protection this would overwrite /etc/passwd
.
The combination of destination_directory
and the entry name (possibly overridden/replaced by entry_path
) is checked for safety before extraction.
If previously you were doing something like this when using Zip::File#extract
dest_dir = '/tmp/my_app'
::Zip::File.open('zipfile.zip') do |zip|
zip.extract('known_entry.txt', "#{dest_dir}/known_entry.txt")
end
then this will still work! But it can also now be written (and looks more readable) as
dest_dir = '/tmp/my_app'
::Zip::File.open('zipfile.zip') do |zip|
zip.extract('known_entry.txt', destination_directory: dest_dir)
end
No changes in functionality, but now uses named parameters for readability:
- def get_output_stream(entry, permission_int = nil, comment = nil,
- extra = nil, compressed_size = nil, crc = nil,
- compression_method = nil, compression_level = nil,
- size = nil, time = nil, &a_proc)
+ def get_output_stream(entry, permissions: nil, comment: nil,
+ extra: nil, compressed_size: nil, crc: nil,
+ compression_method: nil, compression_level: nil,
+ size: nil, time: nil, &a_proc)
No changes in functionality, but now uses named parameters for readability:
- def initialize(*args)
+ def initialize(
+ zipfile = '', name = '',
+ comment: '', size: 0, compressed_size: 0, crc: 0,
+ compression_method: DEFLATED,
+ compression_level: ::Zip.default_compression,
+ time: ::Zip::DOSTime.now, extra: ::Zip::ExtraField.new
+ )
Major Update!
This method now forces extraction into the current working directory unless this is overridden by supplying a different directory via destination_directory
.
- def extract(dest_path = nil, &block)
+ def extract(entry_path = @name, destination_directory: '.', &block)
Entry#extract_v3
for the version 3.x API.
This effectively splits the final location of an extracted entry into two parts:
- the base directory (controlled by
destination_directory
and.
by default); and - the entry path (controlled by
entry_path
and the entry's name by default).
See File#extract, above, for the details and rationale for this change.
If previously you were doing something like this when using Zip::Entry#extract
dest_dir = '/tmp/my_app'
::Zip::File.open('zipfile.zip') do |zip|
zip.entries do |entry|
entry.extract(::File.join(dest_dir, entry.name))
end
end
then this will still work! But it can also now be written (and looks more readable) as
dest_dir = '/tmp/my_app'
::Zip::File.open('zipfile.zip') do |zip|
zip.entries do |entry|
entry.extract(destination_directory: dest_dir)
end
end
New method. A new alias of #time=
New method. A cleaner way to detect an Entry
that has ZIP64 extensions present.
No changes in functionality, but now uses named parameters for readability:
- def initialize(context, offset = 0, decrypter = nil)
+ def initialize(context, offset: 0, decrypter: nil)
No changes in functionality, but now uses named parameters for readability:
- def open(filename_or_io, offset = 0, decrypter = nil)
+ def open(filename_or_io, offset: 0, decrypter: nil)
This method was deprecated in RubyZip version 2.3 and has been removed in version 3.0.
This method now returns the empty string ''
if it is passed zero for number_of_bytes
.
New method. This returns the uncompressed size of the current entry in bytes, or nil
if there is no current entry.
No changes in functionality, but now uses named parameters for readability:
- def initialize(file_name, stream = false, encrypter = nil)
+ def initialize(file_name, stream: false, encrypter: nil)
No changes in functionality, but now uses named parameters for readability:
- def open(file_name, encrypter = nil)
+ def open(file_name, encrypter: nil)
No changes in functionality, but now uses named parameters for readability:
- def write_buffer(io = ::StringIO.new(''), encrypter = nil)
+ def write_buffer(io = ::StringIO.new(''), encrypter: nil)
This method has been removed. Please use #==
instead.