From 7cd263e6a0d53f793ba41b249f4a78f84c4dc016 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 3 Apr 2018 23:45:04 +0100 Subject: [PATCH 001/469] Clean up use of file_name in Zip::File::initialize. --- lib/zip/file.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 6952ba99..6ff3463d 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -69,18 +69,18 @@ def initialize(file_name, create = false, buffer = false, options = {}) @name = file_name @comment = '' @create = create ? true : false # allow any truthy value to mean true - if !buffer && ::File.size?(file_name) + if !buffer && ::File.size?(@name) @create = false - @file_permissions = ::File.stat(file_name).mode - ::File.open(name, 'rb') do |f| + @file_permissions = ::File.stat(@name).mode + ::File.open(@name, 'rb') do |f| read_from_stream(f) end elsif @create @entry_set = EntrySet.new - elsif ::File.zero?(file_name) - raise Error, "File #{file_name} has zero size. Did you mean to pass the create flag?" + elsif ::File.zero?(@name) + raise Error, "File #{@name} has zero size. Did you mean to pass the create flag?" else - raise Error, "File #{file_name} not found" + raise Error, "File #{@name} not found" end @stored_entries = @entry_set.dup @stored_comment = @comment From cfa9441914d56bb866dd70c29fd5ec33bd2a8fc6 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 3 Apr 2018 23:48:54 +0100 Subject: [PATCH 002/469] Handle passing an IO to Zip::File.new better. This now actually extracts the path from the IO if one is passed in. --- lib/zip/file.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 6ff3463d..8cb5f03a 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -64,9 +64,9 @@ class File < CentralDirectory # Opens a zip archive. Pass true as the second parameter to create # a new archive if it doesn't exist already. - def initialize(file_name, create = false, buffer = false, options = {}) + def initialize(path_or_io, create = false, buffer = false, options = {}) super() - @name = file_name + @name = path_or_io.respond_to?(:path) ? path_or_io.path : path_or_io @comment = '' @create = create ? true : false # allow any truthy value to mean true if !buffer && ::File.size?(@name) From 03633933ebb714cdff1e3f5604434f35bad0e0cf Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 4 Apr 2018 14:31:34 +0100 Subject: [PATCH 003/469] No need to require stringio in Zip::File.open_buffer. It's already required in zip.rb. --- lib/zip/file.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 8cb5f03a..dc86bb72 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -120,7 +120,6 @@ def open_buffer(io, options = {}) raise "Zip::File.open_buffer expects a String or IO-like argument (responds to #{IO_METHODS.join(', ')}). Found: #{io.class}" end if io.is_a?(::String) - require 'stringio' io = ::StringIO.new(io) elsif io.respond_to?(:binmode) # https://github.com/rubyzip/rubyzip/issues/119 From 15ccc25da1755200f6d2cb29fde49c303a236073 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 4 Apr 2018 15:12:22 +0100 Subject: [PATCH 004/469] Fix File.open_buffer when no changes are made. Things are now more carefully set up, and if a buffer is passed in which represents a file that already exists then this is taken into account. All initialization is now done in File.new, rather than being split between there and File.open_buffer. This has also needed a bit of a re-write of Zip::File.initialize. I've tried to bring some logic to it as a result, and have added comments to explain what is now happening. --- lib/zip/file.rb | 24 ++++++++++++++++++++---- test/file_test.rb | 5 +++-- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index dc86bb72..c0a44207 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -69,19 +69,33 @@ def initialize(path_or_io, create = false, buffer = false, options = {}) @name = path_or_io.respond_to?(:path) ? path_or_io.path : path_or_io @comment = '' @create = create ? true : false # allow any truthy value to mean true - if !buffer && ::File.size?(@name) + + if ::File.size?(@name.to_s) + # There is a file, which exists, that is associated with this zip. @create = false @file_permissions = ::File.stat(@name).mode - ::File.open(@name, 'rb') do |f| - read_from_stream(f) + + if buffer + read_from_stream(path_or_io) + else + ::File.open(@name, 'rb') do |f| + read_from_stream(f) + end end + elsif buffer && path_or_io.size > 0 + # This zip is probably a non-empty StringIO. + read_from_stream(path_or_io) elsif @create + # This zip is completely new/empty and is to be created. @entry_set = EntrySet.new elsif ::File.zero?(@name) + # A file exists, but it is empty. raise Error, "File #{@name} has zero size. Did you mean to pass the create flag?" else + # Everything is wrong. raise Error, "File #{@name} not found" end + @stored_entries = @entry_set.dup @stored_comment = @comment @restore_ownership = options[:restore_ownership] || false @@ -119,16 +133,18 @@ def open_buffer(io, options = {}) unless IO_METHODS.map { |method| io.respond_to?(method) }.all? || io.is_a?(String) raise "Zip::File.open_buffer expects a String or IO-like argument (responds to #{IO_METHODS.join(', ')}). Found: #{io.class}" end + if io.is_a?(::String) io = ::StringIO.new(io) elsif io.respond_to?(:binmode) # https://github.com/rubyzip/rubyzip/issues/119 io.binmode end + zf = ::Zip::File.new(io, true, true, options) - zf.read_from_stream(io) return zf unless block_given? yield zf + begin zf.write_buffer(io) rescue IOError => e diff --git a/test/file_test.rb b/test/file_test.rb index 32e21e33..53124bea 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -107,13 +107,14 @@ def test_open_buffer_with_stringio def test_close_buffer_with_stringio string_io = StringIO.new File.read('test/data/rubycode.zip') zf = ::Zip::File.open_buffer string_io - assert(zf.close || true) # Poor man's refute_raises + assert_nil zf.close end def test_close_buffer_with_io f = File.open('test/data/rubycode.zip') zf = ::Zip::File.open_buffer f - assert zf.close + refute zf.commit_required? + assert_nil zf.close f.close end From 84c208982f717f10f7133cdfc1f016607398858d Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 4 Apr 2018 15:40:38 +0100 Subject: [PATCH 005/469] Switch newly created StringIOs to binmode. StringIO objects created within File.open_buffer were not being switched into binmode, but those passed in were. Fix this inconsistency and add a test. --- lib/zip/file.rb | 10 ++++------ test/file_test.rb | 7 +++++++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index c0a44207..b5b85eea 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -134,12 +134,10 @@ def open_buffer(io, options = {}) raise "Zip::File.open_buffer expects a String or IO-like argument (responds to #{IO_METHODS.join(', ')}). Found: #{io.class}" end - if io.is_a?(::String) - io = ::StringIO.new(io) - elsif io.respond_to?(:binmode) - # https://github.com/rubyzip/rubyzip/issues/119 - io.binmode - end + io = ::StringIO.new(io) if io.is_a?(::String) + + # https://github.com/rubyzip/rubyzip/issues/119 + io.binmode if io.respond_to?(:binmode) zf = ::Zip::File.new(io, true, true, options) return zf unless block_given? diff --git a/test/file_test.rb b/test/file_test.rb index 53124bea..8bbf7cf8 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -97,6 +97,13 @@ def test_get_output_stream end end + def test_open_buffer_with_string + string = File.read('test/data/rubycode.zip') + ::Zip::File.open_buffer string do |zf| + assert zf.entries.map { |e| e.name }.include?('zippedruby1.rb') + end + end + def test_open_buffer_with_stringio string_io = StringIO.new File.read('test/data/rubycode.zip') ::Zip::File.open_buffer string_io do |zf| From 1e21121f6cdb105ee8d6ab7551950b72120a261f Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Fri, 24 May 2019 17:07:35 +0100 Subject: [PATCH 006/469] Update example_recursive in README The sample has been updated several times since the last update to the README. Also ran through prettier for formatting consistency. --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index d5dbe76b..8255cd90 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # rubyzip + [![Gem Version](https://badge.fury.io/rb/rubyzip.svg)](http://badge.fury.io/rb/rubyzip) [![Build Status](https://secure.travis-ci.org/rubyzip/rubyzip.svg)](http://travis-ci.org/rubyzip/rubyzip) [![Code Climate](https://codeclimate.com/github/rubyzip/rubyzip.svg)](https://codeclimate.com/github/rubyzip/rubyzip) @@ -19,9 +20,10 @@ gem 'zip-zip' # will load compatibility for old rubyzip API. ## Requirements -* Ruby 1.9.2 or greater +- Ruby 1.9.2 or greater ## Installation + Rubyzip is available on RubyGems: ``` @@ -59,7 +61,8 @@ end ``` ### Zipping a directory recursively -Copy from [here](https://github.com/rubyzip/rubyzip/blob/05916bf89181e1955118fd3ea059f18acac28cc8/samples/example_recursive.rb ) + +Copy from [here](https://github.com/rubyzip/rubyzip/blob/9d891f7353e66052283562d3e252fe380bb4b199/samples/example_recursive.rb) ```ruby require 'zip' @@ -83,7 +86,7 @@ class ZipFileGenerator # Zip the input directory. def write - entries = Dir.entries(@input_dir) - %w(. ..) + entries = Dir.entries(@input_dir) - %w[. ..] ::Zip::File.open(@output_file, ::Zip::File::CREATE) do |zipfile| write_entries entries, '', zipfile @@ -97,7 +100,6 @@ class ZipFileGenerator entries.each do |e| zipfile_path = path == '' ? e : File.join(path, e) disk_file_path = File.join(@input_dir, zipfile_path) - puts "Deflating #{disk_file_path}" if File.directory? disk_file_path recursively_deflate_directory(disk_file_path, zipfile, zipfile_path) @@ -109,14 +111,12 @@ class ZipFileGenerator def recursively_deflate_directory(disk_file_path, zipfile, zipfile_path) zipfile.mkdir zipfile_path - subdir = Dir.entries(disk_file_path) - %w(. ..) + subdir = Dir.entries(disk_file_path) - %w[. ..] write_entries subdir, zipfile_path, zipfile end def put_into_archive(disk_file_path, zipfile, zipfile_path) - zipfile.get_output_stream(zipfile_path) do |f| - f.write(File.open(disk_file_path, 'rb').read) - end + zipfile.add(zipfile_path, disk_file_path) end end ``` @@ -177,7 +177,6 @@ But there is one exception when it is not working - General Purpose Flag Bit 3. > If bit 3 (0x08) of the general-purpose flags field is set, then the CRC-32 and file sizes are not known when the header is written. The fields in the local header are filled with zero, and the CRC-32 and size are appended in a 12-byte structure (optionally preceded by a 4-byte signature) immediately after the compressed data - If `::Zip::InputStream` finds such entry in the zip archive it will raise an exception. ### Password Protection (Experimental) @@ -220,7 +219,7 @@ File.open(new_path, "wb") {|f| f.write(buffer.string) } ## Configuration -By default, rubyzip will not overwrite files if they already exist inside of the extracted path. To change this behavior, you may specify a configuration option like so: +By default, rubyzip will not overwrite files if they already exist inside of the extracted path. To change this behavior, you may specify a configuration option like so: ```ruby Zip.on_exists_proc = true @@ -251,6 +250,7 @@ You can set the default compression level like so: ```ruby Zip.default_compression = Zlib::DEFAULT_COMPRESSION ``` + It defaults to `Zlib::DEFAULT_COMPRESSION`. Possible values are `Zlib::BEST_COMPRESSION`, `Zlib::DEFAULT_COMPRESSION` and `Zlib::NO_COMPRESSION` Sometimes file names inside zip contain non-ASCII characters. If you can assume which encoding was used for such names and want to be able to find such entries using `find_entry` then you can force assumed encoding like so: From 952950e474a07ef8fe2f5cf894bad189c6247ac1 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Fri, 24 May 2019 17:25:17 +0100 Subject: [PATCH 007/469] Update changelog for #397 Also run changelog through prettier for consistency with README.md. --- Changelog.md | 399 ++++++++++++++++++++++----------------------------- 1 file changed, 170 insertions(+), 229 deletions(-) diff --git a/Changelog.md b/Changelog.md index 591b7ca0..57fccdf4 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,320 +1,261 @@ -X.X.X (Next) -===== +# X.X.X (Next) +- +Tooling / Documentation -1.2.3 -===== +- Update `example_recursive.rb` in README [#397](https://github.com/rubyzip/rubyzip/pull/397) -* Allow tilde in zip entry names [#391](https://github.com/rubyzip/rubyzip/pull/391) (fixes regression in 1.2.2 from [#376](https://github.com/rubyzip/rubyzip/pull/376)) -* Support frozen string literals in more files [#390](https://github.com/rubyzip/rubyzip/pull/390) -* Require `pathname` explicitly [#388](https://github.com/rubyzip/rubyzip/pull/388) (fixes regression in 1.2.2 from [#376](https://github.com/rubyzip/rubyzip/pull/376)) +# 1.2.3 + +- Allow tilde in zip entry names [#391](https://github.com/rubyzip/rubyzip/pull/391) (fixes regression in 1.2.2 from [#376](https://github.com/rubyzip/rubyzip/pull/376)) +- Support frozen string literals in more files [#390](https://github.com/rubyzip/rubyzip/pull/390) +- Require `pathname` explicitly [#388](https://github.com/rubyzip/rubyzip/pull/388) (fixes regression in 1.2.2 from [#376](https://github.com/rubyzip/rubyzip/pull/376)) Tooling / Documentation: -* CI updates [#392](https://github.com/rubyzip/rubyzip/pull/392), [#394](https://github.com/rubyzip/rubyzip/pull/394) - * Bump supported ruby versions and add 2.6 - * JRuby failures are no longer ignored (reverts [#375](https://github.com/rubyzip/rubyzip/pull/375) / part of [#371](https://github.com/rubyzip/rubyzip/pull/371)) -* Add changelog entry that was missing for last release [#387](https://github.com/rubyzip/rubyzip/pull/387) -* Comment cleanup [#385](https://github.com/rubyzip/rubyzip/pull/385) +- CI updates [#392](https://github.com/rubyzip/rubyzip/pull/392), [#394](https://github.com/rubyzip/rubyzip/pull/394) + - Bump supported ruby versions and add 2.6 + - JRuby failures are no longer ignored (reverts [#375](https://github.com/rubyzip/rubyzip/pull/375) / part of [#371](https://github.com/rubyzip/rubyzip/pull/371)) +- Add changelog entry that was missing for last release [#387](https://github.com/rubyzip/rubyzip/pull/387) +- Comment cleanup [#385](https://github.com/rubyzip/rubyzip/pull/385) -1.2.2 -===== +# 1.2.2 NB: This release drops support for extracting symlinks, because there was no clear way to support this securely. See https://github.com/rubyzip/rubyzip/pull/376#issue-210954555 for details. -* Fix CVE-2018-1000544 [#376](https://github.com/rubyzip/rubyzip/pull/376) / [#371](https://github.com/rubyzip/rubyzip/pull/371) -* Fix NoMethodError: undefined method `glob' [#363](https://github.com/rubyzip/rubyzip/pull/363) -* Fix handling of stored files (i.e. files not using compression) with general purpose bit 3 set [#358](https://github.com/rubyzip/rubyzip/pull/358) -* Fix `close` on StringIO-backed zip file [#353](https://github.com/rubyzip/rubyzip/pull/353) -* Add `Zip.force_entry_names_encoding` option [#340](https://github.com/rubyzip/rubyzip/pull/340) -* Update rubocop, apply auto-fixes, and fix regressions caused by said auto-fixes [#332](https://github.com/rubyzip/rubyzip/pull/332), [#355](https://github.com/rubyzip/rubyzip/pull/355) -* Save temporary files to temporary directory (rather than current directory) [#325](https://github.com/rubyzip/rubyzip/pull/325) +- Fix CVE-2018-1000544 [#376](https://github.com/rubyzip/rubyzip/pull/376) / [#371](https://github.com/rubyzip/rubyzip/pull/371) +- Fix NoMethodError: undefined method `glob' [#363](https://github.com/rubyzip/rubyzip/pull/363) +- Fix handling of stored files (i.e. files not using compression) with general purpose bit 3 set [#358](https://github.com/rubyzip/rubyzip/pull/358) +- Fix `close` on StringIO-backed zip file [#353](https://github.com/rubyzip/rubyzip/pull/353) +- Add `Zip.force_entry_names_encoding` option [#340](https://github.com/rubyzip/rubyzip/pull/340) +- Update rubocop, apply auto-fixes, and fix regressions caused by said auto-fixes [#332](https://github.com/rubyzip/rubyzip/pull/332), [#355](https://github.com/rubyzip/rubyzip/pull/355) +- Save temporary files to temporary directory (rather than current directory) [#325](https://github.com/rubyzip/rubyzip/pull/325) Tooling / Documentation: -* Turn off all terminal output in all tests [#361](https://github.com/rubyzip/rubyzip/pull/361) -* Several CI updates [#346](https://github.com/rubyzip/rubyzip/pull/346), [#347](https://github.com/rubyzip/rubyzip/pull/347), [#350](https://github.com/rubyzip/rubyzip/pull/350), [#352](https://github.com/rubyzip/rubyzip/pull/352) -* Several README improvements [#345](https://github.com/rubyzip/rubyzip/pull/345), [#326](https://github.com/rubyzip/rubyzip/pull/326), [#321](https://github.com/rubyzip/rubyzip/pull/321) - -1.2.1 -===== - -* Add accessor to @internal_file_attributes #304 -* Extended globbing #303 -* README updates #283, #289 -* Cleanup after tests #298, #306 -* Fix permissions on new zip files #294, #300 -* Fix examples #297 -* Support cp932 encoding #308 -* Fix Directory traversal vulnerability #315 -* Allow open_buffer to work without a given block #314 +- Turn off all terminal output in all tests [#361](https://github.com/rubyzip/rubyzip/pull/361) +- Several CI updates [#346](https://github.com/rubyzip/rubyzip/pull/346), [#347](https://github.com/rubyzip/rubyzip/pull/347), [#350](https://github.com/rubyzip/rubyzip/pull/350), [#352](https://github.com/rubyzip/rubyzip/pull/352) +- Several README improvements [#345](https://github.com/rubyzip/rubyzip/pull/345), [#326](https://github.com/rubyzip/rubyzip/pull/326), [#321](https://github.com/rubyzip/rubyzip/pull/321) -1.2.0 -===== +# 1.2.1 -* Don't enable JRuby objectspace #252 -* Fixes an exception thrown when decoding some weird .zip files #248 -* Use duck typing with IO methods #244 -* Added error for empty (zero bit) zip file #242 -* Accept StringIO in Zip.open_buffer #238 -* Do something more expected with new file permissions #237 -* Case insensitivity option for #find_entry #222 -* Fixes in documentation and examples +- Add accessor to @internal_file_attributes #304 +- Extended globbing #303 +- README updates #283, #289 +- Cleanup after tests #298, #306 +- Fix permissions on new zip files #294, #300 +- Fix examples #297 +- Support cp932 encoding #308 +- Fix Directory traversal vulnerability #315 +- Allow open_buffer to work without a given block #314 -1.1.7 -===== +# 1.2.0 -* Fix UTF-8 support for comments -* `Zip.sort_entries` working for zip output -* Prevent tempfile path from being unlinked by garbage collection -* NTFS Extra Field (0x000a) support -* Use String#tr instead of String#gsub -* Ability to not show warning about incorrect date -* Be smarter about handling buffer file modes. -* Support for Traditional Encryption (ZipCrypto) +- Don't enable JRuby objectspace #252 +- Fixes an exception thrown when decoding some weird .zip files #248 +- Use duck typing with IO methods #244 +- Added error for empty (zero bit) zip file #242 +- Accept StringIO in Zip.open_buffer #238 +- Do something more expected with new file permissions #237 +- Case insensitivity option for #find_entry #222 +- Fixes in documentation and examples -1.1.6 -===== +# 1.1.7 -* Revert "Return created zip file from Zip::File.open when supplied a block" +- Fix UTF-8 support for comments +- `Zip.sort_entries` working for zip output +- Prevent tempfile path from being unlinked by garbage collection +- NTFS Extra Field (0x000a) support +- Use String#tr instead of String#gsub +- Ability to not show warning about incorrect date +- Be smarter about handling buffer file modes. +- Support for Traditional Encryption (ZipCrypto) -1.1.5 -===== +# 1.1.6 -* Treat empty file as non-exists (@layerssss) -* Revert regression commit -* Return created zip file from Zip::File.open when supplied a block (@tpickett66) -* Zip::Entry::DEFLATED is forced on every file (@mehmetc) -* Add InputStream#ungetc (@zacstewart) -* Alias for legacy error names (@orien) +- Revert "Return created zip file from Zip::File.open when supplied a block" -1.1.4 -===== +# 1.1.5 -* Don't send empty string to stream (@mrloop) -* Zip::Entry::DEFLATED was forced on every file (@mehmetc) -* Alias for legacy error names (@orien) +- Treat empty file as non-exists (@layerssss) +- Revert regression commit +- Return created zip file from Zip::File.open when supplied a block (@tpickett66) +- Zip::Entry::DEFLATED is forced on every file (@mehmetc) +- Add InputStream#ungetc (@zacstewart) +- Alias for legacy error names (@orien) -1.1.3 -===== +# 1.1.4 -* Fix compatibility of ::OutputStream::write_buffer (@orien) -* Clean up tempfiles from output stream (@iangreenleaf) +- Don't send empty string to stream (@mrloop) +- Zip::Entry::DEFLATED was forced on every file (@mehmetc) +- Alias for legacy error names (@orien) -1.1.2 -===== +# 1.1.3 -* Fix compatibility of ::Zip::File.write_buffer +- Fix compatibility of ::OutputStream::write_buffer (@orien) +- Clean up tempfiles from output stream (@iangreenleaf) -1.1.1 -===== +# 1.1.2 -* Speedup deflater (@loadhigh) -* Less Arrays and Strings allocations (@srawlins) -* Fix Zip64 writting support (@mrjamesriley) -* Fix StringIO support (@simonoff) -* Posibility to change default compression level -* Make Zip64 write support optional via configuration +- Fix compatibility of ::Zip::File.write_buffer -1.1.0 -===== +# 1.1.1 -* StringIO Support -* Zip64 Support -* Better jRuby Support -* Order of files in the archive can be sorted -* Other small fixes +- Speedup deflater (@loadhigh) +- Less Arrays and Strings allocations (@srawlins) +- Fix Zip64 writing support (@mrjamesriley) +- Fix StringIO support (@simonoff) +- Possibility to change default compression level +- Make Zip64 write support optional via configuration -1.0.0 -===== +# 1.1.0 -* Removed support for Ruby 1.8 -* Changed the API for gem. Now it can be used without require param in Gemfile. -* Added read-only support for Zip64 files. -* Added support for setting Unicode file names. +- StringIO Support +- Zip64 Support +- Better jRuby Support +- Order of files in the archive can be sorted +- Other small fixes -0.9.9 -===== +# 1.0.0 -* Added support for backslashes in zip files (generated by the default Windows zip packer for example) and comment sections with the comment length set to zero even though there is actually a comment. +- Removed support for Ruby 1.8 +- Changed the API for gem. Now it can be used without require param in Gemfile. +- Added read-only support for Zip64 files. +- Added support for setting Unicode file names. -0.9.8 -===== +# 0.9.9 -* Fixed: "Unitialized constant NullInputStream" error +- Added support for backslashes in zip files (generated by the default Windows zip packer for example) and comment sections with the comment length set to zero even though there is actually a comment. -0.9.5 -===== +# 0.9.8 -* Removed support for loading ruby in zip files (ziprequire.rb). +- Fixed: "Unitialized constant NullInputStream" error -0.9.4 -===== +# 0.9.5 -* Changed ZipOutputStream.put_next_entry signature (API CHANGE!). Now allows comment, extra field and compression method to be specified. +- Removed support for loading ruby in zip files (ziprequire.rb). -0.9.3 -===== +# 0.9.4 -* Fixed: Added ZipEntry::name_encoding which retrieves the character -encoding of the name and comment of the entry. -* Added convenience methods ZipEntry::name_in(enc) and ZipEntry::comment_in(enc) for -getting zip entry names and comments in a specified character -encoding. +- Changed ZipOutputStream.put_next_entry signature (API CHANGE!). Now allows comment, extra field and compression method to be specified. -0.9.2 -===== +# 0.9.3 -* Fixed: Renaming an entry failed if the entry's new name was a different length than its old name. (Diego Barros) +- Fixed: Added ZipEntry::name_encoding which retrieves the character encoding of the name and comment of the entry. +- Added convenience methods ZipEntry::name_in(enc) and ZipEntry::comment_in(enc) for getting zip entry names and comments in a specified character encoding. -0.9.1 -===== +# 0.9.2 -* Added symlink support and support for unix file permissions. Reduced memory usage during decompression. -* New methods ZipFile::[follow_symlinks, restore_times, restore_permissions, restore_ownership]. -* New methods ZipEntry::unix_perms, ZipInputStream::eof?. -* Added documentation and test for new ZipFile::extract. -* Added some of the API suggestions from sf.net #1281314. -* Applied patch for sf.net bug #1446926. -* Applied patch for sf.net bug #1459902. -* Rework ZipEntry and delegate classes. +- Fixed: Renaming an entry failed if the entry's new name was a different length than its old name. (Diego Barros) -0.5.12 -====== +# 0.9.1 -* Fixed problem with writing binary content to a ZipFile in MS Windows. +- Added symlink support and support for unix file permissions. Reduced memory usage during decompression. +- New methods ZipFile::[follow_symlinks, restore_times, restore_permissions, restore_ownership]. +- New methods ZipEntry::unix_perms, ZipInputStream::eof?. +- Added documentation and test for new ZipFile::extract. +- Added some of the API suggestions from sf.net #1281314. +- Applied patch for sf.net bug #1446926. +- Applied patch for sf.net bug #1459902. +- Rework ZipEntry and delegate classes. -0.5.11 -====== +# 0.5.12 -* Fixed name clash file method copy_stream from fileutils.rb. Fixed problem with references to constant CHUNK_SIZE. -* ZipInputStream/AbstractInputStream read is now buffered like ruby IO's read method, which means that read and gets etc can be mixed. The - unbuffered read method has been renamed to sysread. +- Fixed problem with writing binary content to a ZipFile in MS Windows. -0.5.10 -====== +# 0.5.11 -* Fixed method name resolution problem with FileUtils::copy_stream and IOExtras::copy_stream. +- Fixed name clash file method copy_stream from fileutils.rb. Fixed problem with references to constant CHUNK_SIZE. +- ZipInputStream/AbstractInputStream read is now buffered like ruby IO's read method, which means that read and gets etc can be mixed. The unbuffered read method has been renamed to sysread. -0.5.9 -===== +# 0.5.10 -* Fixed serious memory consumption issue +- Fixed method name resolution problem with FileUtils::copy_stream and IOExtras::copy_stream. -0.5.8 -===== +# 0.5.9 -* Fixed install script. +- Fixed serious memory consumption issue -0.5.7 -===== -* install.rb no longer assumes it is being run from the toplevel source -dir. Directory structure changed to reflect common ruby library -project structure. Migrated from RubyUnit to Test::Unit format. Now -uses Rake to build source packages and gems and run unit tests. +# 0.5.8 -0.5.6 -===== -* Fix for FreeBSD 4.9 which returns Errno::EFBIG instead of -Errno::EINVAL for some invalid seeks. Fixed 'version needed to -extract'-field incorrect in local headers. +- Fixed install script. -0.5.5 -===== +# 0.5.7 -* Fix for a problem with writing zip files that concerns only ruby 1.8.1. +- install.rb no longer assumes it is being run from the toplevel source dir. Directory structure changed to reflect common ruby library project structure. Migrated from RubyUnit to Test::Unit format. Now uses Rake to build source packages and gems and run unit tests. -0.5.4 -===== +# 0.5.6 -* Significantly reduced memory footprint when modifying zip files. +- Fix for FreeBSD 4.9 which returns Errno::EFBIG instead of Errno::EINVAL for some invalid seeks. Fixed 'version needed to extract'-field incorrect in local headers. -0.5.3 -===== -* Added optimization to avoid decompressing and recompressing individual -entries when modifying a zip archive. +# 0.5.5 -0.5.2 -===== -* Fixed ZipFile corruption bug in ZipFile class. Added basic unix -extra-field support. +- Fix for a problem with writing zip files that concerns only ruby 1.8.1. -0.5.1 -===== +# 0.5.4 -* Fixed ZipFile.get_output_stream bug. +- Significantly reduced memory footprint when modifying zip files. -0.5.0 -===== +# 0.5.3 -* Ruby 1.8.0 and ruby-zlib 0.6.0 compatibility -* Changed method names from camelCase to rubys underscore style. -* Installs to zip/ subdir instead of directly to site_ruby -* Added ZipFile.directory and ZipFile.file - each method return an -object that can be used like Dir and File only for the contents of the -zip file. -* Added sample application zipfind which works like Find.find, only -Zip::ZipFind.find traverses into zip archives too. -* FIX: AbstractInputStream.each_line with non-default separator +- Added optimization to avoid decompressing and recompressing individual entries when modifying a zip archive. +# 0.5.2 -0.5.0a -====== -Source reorganized. Added ziprequire, which can be used to load ruby -modules from a zip file, in a fashion similar to jar files in -Java. Added gtk_ruby_zip, another sample application. Implemented -ZipInputStream.lineno and ZipInputStream.rewind +- Fixed ZipFile corruption bug in ZipFile class. Added basic unix extra-field support. -Bug fixes: +# 0.5.1 + +- Fixed ZipFile.get_output_stream bug. -* Read and write date and time information correctly for zip entries. -* Fixed read() using separate buffer, causing mix of gets/readline/read to -cause problems. +# 0.5.0 -0.4.2 -===== +- Ruby 1.8.0 and ruby-zlib 0.6.0 compatibility +- Changed method names from camelCase to rubys underscore style. +- Installs to zip/ subdir instead of directly to site_ruby +- Added ZipFile.directory and ZipFile.file - each method return an + object that can be used like Dir and File only for the contents of the + zip file. +- Added sample application zipfind which works like Find.find, only + Zip::ZipFind.find traverses into zip archives too. +- FIX: AbstractInputStream.each_line with non-default separator -* Performance optimizations. Test suite runs in half the time. +# 0.5.0a + +Source reorganized. Added ziprequire, which can be used to load ruby modules from a zip file, in a fashion similar to jar files in Java. Added gtk_ruby_zip, another sample application. Implemented ZipInputStream.lineno and ZipInputStream.rewind + +Bug fixes: -0.4.1 -===== +- Read and write date and time information correctly for zip entries. +- Fixed read() using separate buffer, causing mix of gets/readline/read to cause problems. -* Windows compatibility fixes. +# 0.4.2 -0.4.0 -===== +- Performance optimizations. Test suite runs in half the time. -* Zip::ZipFile is now mutable and provides a more convenient way of -modifying zip archives than Zip::ZipOutputStream. Operations for -adding, extracting, renaming, replacing and removing entries to zip -archives are now available. +# 0.4.1 -* Runs without warnings with -w switch. +- Windows compatibility fixes. -* Install script install.rb added. +# 0.4.0 -0.3.1 -===== +- Zip::ZipFile is now mutable and provides a more convenient way of modifying zip archives than Zip::ZipOutputStream. Operations for adding, extracting, renaming, replacing and removing entries to zip archives are now available. +- Runs without warnings with -w switch. +- Install script install.rb added. -* Rudimentary support for writing zip archives. +# 0.3.1 -0.2.2 -===== +- Rudimentary support for writing zip archives. -* Fixed and extended unit test suite. Updated to work with ruby/zlib -0.5. It doesn't work with earlier versions of ruby/zlib. +# 0.2.2 -0.2.0 -===== +- Fixed and extended unit test suite. Updated to work with ruby/zlib 0.5. It doesn't work with earlier versions of ruby/zlib. -* Class ZipFile added. Where ZipInputStream is used to read the -individual entries in a zip file, ZipFile reads the central directory -in the zip archive, so you can get to any entry in the zip archive -without having to skipping through all the preceeding entries. +# 0.2.0 +- Class ZipFile added. Where ZipInputStream is used to read the individual entries in a zip file, ZipFile reads the central directory in the zip archive, so you can get to any entry in the zip archive without having to skipping through all the preceeding entries. -0.1.0 -===== +# 0.1.0 -* First working version of ZipInputStream. +- First working version of ZipInputStream. From 5152f6f7a0f5515d0fe1717d0c3dcb40c26ab2c9 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Sun, 7 Jul 2019 17:59:35 +0100 Subject: [PATCH 008/469] Put CI back to trusty Xenial is now the default. Trusty is now out of support but still not end of life. Also omit the ruby patch versions so we don't have to keep updating them. --- .travis.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 00f3b2d6..6b7d6e05 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,14 @@ language: ruby +dist: trusty cache: bundler rvm: - - 2.0.0 - - 2.1.10 - - 2.2.10 - - 2.3.8 - - 2.4.5 - - 2.5.5 - - 2.6.2 + - 2.0 + - 2.1 + - 2.2 + - 2.3 + - 2.4 + - 2.5 + - 2.6 - ruby-head matrix: include: From b2573f6069ef1eecb440d23c93015dfa011d283a Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Sun, 7 Jul 2019 18:18:49 +0100 Subject: [PATCH 009/469] Use rbx-4 in CI --- .travis.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6b7d6e05..d98d0e27 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,12 +18,10 @@ matrix: jdk: openjdk7 - rvm: jruby-head jdk: oraclejdk8 - - rvm: rbx-3 - env: - - RUBYOPT="-rbundler/deprecate" + - rvm: rbx-4 allow_failures: - rvm: ruby-head - - rvm: rbx-3 + - rvm: rbx-4 - rvm: jruby-head before_install: - gem --version From fc23db2efc8ba7b39e5ef94ddbd0bf23a4d5ba5e Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Sat, 20 Jul 2019 15:06:17 +0100 Subject: [PATCH 010/469] Update changelog for #399 --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index 57fccdf4..1240b3de 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,6 +5,7 @@ Tooling / Documentation - Update `example_recursive.rb` in README [#397](https://github.com/rubyzip/rubyzip/pull/397) +- Fix CI on `trusty` for now, and automatically pick the latest ruby patch version [#399](https://github.com/rubyzip/rubyzip/pull/399) # 1.2.3 From 8dfc95dc79c93c0a4c10cf9407784bc736600564 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Sat, 20 Jul 2019 15:15:30 +0100 Subject: [PATCH 011/469] Hold jruby at 9.1 on JDK 7 --- .travis.yml | 2 +- Changelog.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index d98d0e27..358e2a8a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ matrix: include: - rvm: jruby jdk: oraclejdk8 - - rvm: jruby + - rvm: jruby-9.1 jdk: openjdk7 - rvm: jruby-head jdk: oraclejdk8 diff --git a/Changelog.md b/Changelog.md index 1240b3de..8bb4f6dd 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,7 +5,7 @@ Tooling / Documentation - Update `example_recursive.rb` in README [#397](https://github.com/rubyzip/rubyzip/pull/397) -- Fix CI on `trusty` for now, and automatically pick the latest ruby patch version [#399](https://github.com/rubyzip/rubyzip/pull/399) +- Hold CI at `trusty` for now, automatically pick the latest ruby patch version, use rbx-4 and hold jruby at 9.1 [#399](https://github.com/rubyzip/rubyzip/pull/399) # 1.2.3 From eeef5073d58253e2044dbf81d1b205efd590b59a Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Thu, 5 Sep 2019 19:00:34 +0100 Subject: [PATCH 012/469] Add test case based on #146 --- test/file_test.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/file_test.rb b/test/file_test.rb index 3c52c778..f2d248e3 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -131,6 +131,15 @@ def test_close_buffer_with_io f.close end + def test_open_buffer_with_io_and_block + File.open('test/data/rubycode.zip') do |io| + io.set_encoding(Encoding::BINARY) # not strictly required but can be set + Zip::File.open_buffer(io) do |zip_io| + # left empty on purpose + end + end + end + def test_open_buffer_without_block string_io = StringIO.new File.read('test/data/rubycode.zip') zf = ::Zip::File.open_buffer string_io From 9a41ce65c432bf90e30824d7a6b60f9a75ccfe0d Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Fri, 6 Sep 2019 17:58:38 +0100 Subject: [PATCH 013/469] Add more explicit test for #280 --- test/file_test.rb | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/test/file_test.rb b/test/file_test.rb index f2d248e3..abe4e4a6 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -123,12 +123,40 @@ def test_close_buffer_with_stringio assert_nil zf.close end - def test_close_buffer_with_io - f = File.open('test/data/rubycode.zip') - zf = ::Zip::File.open_buffer f - refute zf.commit_required? - assert_nil zf.close - f.close + def test_open_buffer_no_op_does_not_change_file + Dir.mktmpdir do |tmp| + test_zip = File.join(tmp, 'test.zip') + FileUtils.cp 'test/data/rubycode.zip', test_zip + + # Note: this may change the file if it is opened with r+b instead of rb. + # The 'extra fields' in this particular zip file get reordered. + File.open(test_zip, 'rb') do |file| + Zip::File.open_buffer(file) do |zf| + nil # do nothing + end + end + + assert_equal \ + File.binread('test/data/rubycode.zip'), + File.binread(test_zip) + end + end + + def test_open_buffer_close_does_not_change_file + Dir.mktmpdir do |tmp| + test_zip = File.join(tmp, 'test.zip') + FileUtils.cp 'test/data/rubycode.zip', test_zip + + File.open(test_zip, 'rb') do |file| + zf = Zip::File.open_buffer(file) + refute zf.commit_required? + assert_nil zf.close + end + + assert_equal \ + File.binread('test/data/rubycode.zip'), + File.binread(test_zip) + end end def test_open_buffer_with_io_and_block From 0d85cb6a49cce7ef51186e64c8f3f147d0fd2b72 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Fri, 6 Sep 2019 18:01:30 +0100 Subject: [PATCH 014/469] Bump to 1.2.4 --- Changelog.md | 4 ++++ lib/zip/version.rb | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 8bb4f6dd..50fc6e5b 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,10 @@ - +# 1.2.4 (2019-09-06) + +- Do not rewrite zip files opened with `open_buffer` that have not changed [#360](https://github.com/rubyzip/rubyzip/pull/360) + Tooling / Documentation - Update `example_recursive.rb` in README [#397](https://github.com/rubyzip/rubyzip/pull/397) diff --git a/lib/zip/version.rb b/lib/zip/version.rb index 4d6ab8b3..afbccee8 100644 --- a/lib/zip/version.rb +++ b/lib/zip/version.rb @@ -1,3 +1,3 @@ module Zip - VERSION = '1.2.3' + VERSION = '1.2.4' end From 72e7ca0d04de580e31717555db20d340c69e68de Mon Sep 17 00:00:00 2001 From: Orien Madgwick <_@orien.io> Date: Thu, 12 Sep 2019 12:56:00 +1000 Subject: [PATCH 015/469] Add project metadata to the gemspec As per https://guides.rubygems.org/specification-reference/#metadata, add metadata to the gemspec file. This'll allow people to more easily access the source code, raise issues and read the changelog. These `bug_tracker_uri`, `changelog_uri`, `documentation_uri`, `wiki_uri` and `source_code_uri` links will appear on the rubygems page at https://rubygems.org/gems/rubyzip and be available via the rubygems API after the next release. --- rubyzip.gemspec | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rubyzip.gemspec b/rubyzip.gemspec index 4ca36c2d..6b873752 100644 --- a/rubyzip.gemspec +++ b/rubyzip.gemspec @@ -16,6 +16,13 @@ Gem::Specification.new do |s| s.test_files = Dir.glob('test/**/*') s.require_paths = ['lib'] s.license = 'BSD 2-Clause' + s.metadata = { + 'bug_tracker_uri' => 'https://github.com/rubyzip/rubyzip/issues', + 'changelog_uri' => "https://github.com/rubyzip/rubyzip/blob/v#{s.version}/Changelog.md", + 'documentation_uri' => "https://www.rubydoc.info/gems/rubyzip/#{s.version}", + 'source_code_uri' => "https://github.com/rubyzip/rubyzip/tree/v#{s.version}", + 'wiki_uri' => 'https://github.com/rubyzip/rubyzip/wiki' + } s.required_ruby_version = '>= 1.9.2' s.add_development_dependency 'rake', '~> 10.3' s.add_development_dependency 'pry', '~> 0.10' From ecb277621852589ecc1557f228665a5338ac0809 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 20 May 2018 15:34:55 +0100 Subject: [PATCH 016/469] Zip::File.add_stored() to add uncompressed files. Adding uncompressed files to a zip archive can be overly complex, so this convenience method makes it easier. --- lib/zip/file.rb | 7 +++++++ test/file_test.rb | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index b5b85eea..9c7f3cbd 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -287,6 +287,13 @@ def add(entry, src_path, &continue_on_exists_proc) @entry_set << new_entry end + # Convenience method for adding the contents of a file to the archive + # in Stored format (uncompressed) + def add_stored(entry, src_path, &continue_on_exists_proc) + entry = ::Zip::Entry.new(@name, entry.to_s, nil, nil, nil, nil, ::Zip::Entry::STORED) + add(entry, src_path, &continue_on_exists_proc) + end + # Removes the specified entry. def remove(entry) @entry_set.delete(get_entry(entry)) diff --git a/test/file_test.rb b/test/file_test.rb index abe4e4a6..3b53c2b9 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -204,6 +204,25 @@ def test_add zfRead.get_input_stream(entryName) { |zis| zis.read }) end + def test_add_stored + srcFile = 'test/data/file2.txt' + entryName = 'newEntryName.rb' + assert(::File.exist?(srcFile)) + zf = ::Zip::File.new(EMPTY_FILENAME, ::Zip::File::CREATE) + zf.add_stored(entryName, srcFile) + zf.close + + zfRead = ::Zip::File.new(EMPTY_FILENAME) + entry = zfRead.entries.first + assert_equal('', zfRead.comment) + assert_equal(1, zfRead.entries.length) + assert_equal(entryName, entry.name) + assert_equal(entry.size, entry.compressed_size) + assert_equal(::Zip::Entry::STORED, entry.compression_method) + AssertEntry.assert_contents(srcFile, + zfRead.get_input_stream(entryName) { |zis| zis.read }) + end + def test_recover_permissions_after_add_files_to_archive srcZip = TEST_ZIP.zip_name ::File.chmod(0o664, srcZip) From 93505ca16f0444bdb04f88f4b8f820ae5d628353 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Sun, 15 Sep 2019 14:58:13 +0100 Subject: [PATCH 017/469] Check expected entry size in add_stored test --- test/file_test.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/file_test.rb b/test/file_test.rb index 3b53c2b9..94ff769c 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -217,6 +217,7 @@ def test_add_stored assert_equal('', zfRead.comment) assert_equal(1, zfRead.entries.length) assert_equal(entryName, entry.name) + assert_equal(File.size(srcFile), entry.size) assert_equal(entry.size, entry.compressed_size) assert_equal(::Zip::Entry::STORED, entry.compression_method) AssertEntry.assert_contents(srcFile, From 94b7fa276992933592d69eb6bb17fc09105f8395 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Sun, 15 Sep 2019 15:03:19 +0100 Subject: [PATCH 018/469] [ci skip] Update changelog --- Changelog.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 50fc6e5b..e8a7e16b 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,10 @@ # X.X.X (Next) -- +- Add `add_stored` method to simplify adding entries without compression [#366](https://github.com/rubyzip/rubyzip/pull/366) + +Tooling / Documentation + +- Add more gem metadata links [#402](https://github.com/rubyzip/rubyzip/pull/402) # 1.2.4 (2019-09-06) From 4167f0ce67e42b082605bca75c7bdfd01eb23804 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Thu, 12 Sep 2019 22:01:38 +0100 Subject: [PATCH 019/469] Validate entry sizes when extracting --- README.md | 67 +++++++++++++++++++++++++++++++-------- lib/zip.rb | 4 ++- lib/zip/entry.rb | 7 ++++ lib/zip/errors.rb | 1 + test/file_extract_test.rb | 62 ++++++++++++++++++++++++++++++++++++ 5 files changed, 127 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 8255cd90..2ff41ed9 100644 --- a/README.md +++ b/README.md @@ -152,12 +152,15 @@ When modifying a zip archive the file permissions of the archive are preserved. ### Reading a Zip file ```ruby +MAX_SIZE = 1024**2 # 1MiB (but of course you can increase this) Zip::File.open('foo.zip') do |zip_file| # Handle entries one by one zip_file.each do |entry| - # Extract to file/directory/symlink puts "Extracting #{entry.name}" - entry.extract(dest_file) + raise 'File too large when extracted' if entry.size > MAX_SIZE + + # Extract to file or directory based on name in the archive + entry.extract # Read into memory content = entry.get_input_stream.read @@ -165,6 +168,7 @@ Zip::File.open('foo.zip') do |zip_file| # Find specific entry entry = zip_file.glob('*.csv').first + raise 'File too large when extracted' if entry.size > MAX_SIZE puts entry.get_input_stream.read end ``` @@ -219,6 +223,8 @@ File.open(new_path, "wb") {|f| f.write(buffer.string) } ## Configuration +### Existing Files + By default, rubyzip will not overwrite files if they already exist inside of the extracted path. To change this behavior, you may specify a configuration option like so: ```ruby @@ -233,18 +239,57 @@ Additionally, if you want to configure rubyzip to overwrite existing files while Zip.continue_on_exists_proc = true ``` +### Non-ASCII Names + If you want to store non-english names and want to open them on Windows(pre 7) you need to set this option: ```ruby Zip.unicode_names = true ``` +Sometimes file names inside zip contain non-ASCII characters. If you can assume which encoding was used for such names and want to be able to find such entries using `find_entry` then you can force assumed encoding like so: + +```ruby +Zip.force_entry_names_encoding = 'UTF-8' +``` + +Allowed encoding names are the same as accepted by `String#force_encoding` + +### Date Validation + Some zip files might have an invalid date format, which will raise a warning. You can hide this warning with the following setting: ```ruby Zip.warn_invalid_date = false ``` +### Size Validation + +By default, `rubyzip`'s `extract` method checks that an entry's reported uncompressed size is not (significantly) smaller than its actual size. This is to help you protect your application against [zip bombs](https://en.wikipedia.org/wiki/Zip_bomb). Before `extract`ing an entry, you should check that its size is in the range you expect. For example, if your application supports processing up to 100 files at once, each up to 10MiB, your zip extraction code might look like: + +```ruby +MAX_FILE_SIZE = 10 * 1024**2 # 10MiB +MAX_FILES = 100 +Zip::File.open('foo.zip') do |zip_file| + num_files = 0 + zip_file.each do |entry| + num_files += 1 if entry.file? + raise 'Too many extracted files' if num_files > MAX_FILES + raise 'File too large when extracted' if entry.size > MAX_FILE_SIZE + entry.extract + end +end +``` + +If you need to extract zip files that report incorrect uncompressed sizes and you really trust them not too be too large, you can disable this setting with +```ruby +Zip.validate_entry_sizes = false +``` + +Note that if you use the lower level `Zip::InputStream` interface, `rubyzip` does *not* check the entry `size`s. In this case, the caller is responsible for making sure it does not read more data than expected from the input stream. + +### Default Compression + You can set the default compression level like so: ```ruby @@ -253,13 +298,17 @@ Zip.default_compression = Zlib::DEFAULT_COMPRESSION It defaults to `Zlib::DEFAULT_COMPRESSION`. Possible values are `Zlib::BEST_COMPRESSION`, `Zlib::DEFAULT_COMPRESSION` and `Zlib::NO_COMPRESSION` -Sometimes file names inside zip contain non-ASCII characters. If you can assume which encoding was used for such names and want to be able to find such entries using `find_entry` then you can force assumed encoding like so: +### Zip64 Support + +By default, Zip64 support is disabled for writing. To enable it do this: ```ruby -Zip.force_entry_names_encoding = 'UTF-8' +Zip.write_zip64_support = true ``` -Allowed encoding names are the same as accepted by `String#force_encoding` +_NOTE_: If you will enable Zip64 writing then you will need zip extractor with Zip64 support to extract archive. + +### Block Form You can set multiple settings at the same time by using a block: @@ -272,14 +321,6 @@ You can set multiple settings at the same time by using a block: end ``` -By default, Zip64 support is disabled for writing. To enable it do this: - -```ruby -Zip.write_zip64_support = true -``` - -_NOTE_: If you will enable Zip64 writing then you will need zip extractor with Zip64 support to extract archive. - ## Developing To run the test you need to do this: diff --git a/lib/zip.rb b/lib/zip.rb index 9145207b..c3a6ed5e 100644 --- a/lib/zip.rb +++ b/lib/zip.rb @@ -42,7 +42,8 @@ module Zip :write_zip64_support, :warn_invalid_date, :case_insensitive_match, - :force_entry_names_encoding + :force_entry_names_encoding, + :validate_entry_sizes def reset! @_ran_once = false @@ -54,6 +55,7 @@ def reset! @write_zip64_support = false @warn_invalid_date = true @case_insensitive_match = false + @validate_entry_sizes = true end def setup diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 80160b57..bd3e4f34 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -603,9 +603,16 @@ def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exi get_input_stream do |is| set_extra_attributes_on_path(dest_path) + bytes_written = 0 buf = ''.dup while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf)) os << buf + + next unless ::Zip.validate_entry_sizes + bytes_written += buf.bytesize + if bytes_written > size + raise ::Zip::EntrySizeError, "Entry #{name} should be #{size}B but is larger when inflated" + end end end end diff --git a/lib/zip/errors.rb b/lib/zip/errors.rb index b2bcccd2..364c6eee 100644 --- a/lib/zip/errors.rb +++ b/lib/zip/errors.rb @@ -4,6 +4,7 @@ class EntryExistsError < Error; end class DestinationFileExistsError < Error; end class CompressionMethodError < Error; end class EntryNameError < Error; end + class EntrySizeError < Error; end class InternalError < Error; end class GPFBit3Error < Error; end diff --git a/test/file_extract_test.rb b/test/file_extract_test.rb index 57833fcb..6103aeae 100644 --- a/test/file_extract_test.rb +++ b/test/file_extract_test.rb @@ -10,6 +10,10 @@ def setup ::File.delete(EXTRACTED_FILENAME) if ::File.exist?(EXTRACTED_FILENAME) end + def teardown + ::Zip.reset! + end + def test_extract ::Zip::File.open(TEST_ZIP.zip_name) do |zf| zf.extract(ENTRY_TO_EXTRACT, EXTRACTED_FILENAME) @@ -80,4 +84,62 @@ def test_extract_non_entry_2 end assert(!File.exist?(outFile)) end + + def test_extract_incorrect_size + # The uncompressed size fields in the zip file cannot be trusted. This makes + # it harder for callers to validate the sizes of the files they are + # extracting, which can lead to denial of service. See also + # https://en.wikipedia.org/wiki/Zip_bomb + Dir.mktmpdir do |tmp| + real_zip = File.join(tmp, 'real.zip') + fake_zip = File.join(tmp, 'fake.zip') + file_name = 'a' + true_size = 500_000 + fake_size = 1 + + ::Zip::File.open(real_zip, ::Zip::File::CREATE) do |zf| + zf.get_output_stream(file_name) do |os| + os.write 'a' * true_size + end + end + + compressed_size = nil + ::Zip::File.open(real_zip) do |zf| + a_entry = zf.find_entry(file_name) + compressed_size = a_entry.compressed_size + assert_equal true_size, a_entry.size + end + + true_size_bytes = [compressed_size, true_size, file_name.size].pack('LLS') + fake_size_bytes = [compressed_size, fake_size, file_name.size].pack('LLS') + + data = File.binread(real_zip) + assert data.include?(true_size_bytes) + data.gsub! true_size_bytes, fake_size_bytes + + File.open(fake_zip, 'wb') do |file| + file.write data + end + + Dir.chdir tmp do + ::Zip::File.open(fake_zip) do |zf| + a_entry = zf.find_entry(file_name) + assert_equal fake_size, a_entry.size + + ::Zip.validate_entry_sizes = false + a_entry.extract + assert_equal true_size, File.size(file_name) + FileUtils.rm file_name + + ::Zip.validate_entry_sizes = true + error = assert_raises ::Zip::EntrySizeError do + a_entry.extract + end + assert_equal \ + 'Entry a should be 1B but is larger when inflated', + error.message + end + end + end + end end From 7849f7362ab0cd23d5730ef8b6f2c39252da2285 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Sun, 15 Sep 2019 15:23:35 +0100 Subject: [PATCH 020/469] Default validate_entry_sizes to false for 1.3 release --- Changelog.md | 11 +++++++++++ README.md | 8 +++++++- lib/zip.rb | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/Changelog.md b/Changelog.md index e8a7e16b..45f14333 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,16 @@ # X.X.X (Next) +- + +# 1.3.0 (Next) + +Security + +- Add `validate_entry_sizes` option so that callers can trust an entry's reported size when using `extract` [#403](https://github.com/rubyzip/rubyzip/pull/403) + - This option defaults to `false` for backward compatibility in this release, but you are strongly encouraged to set it to `true`. It will default to `true` in rubyzip 2.0. + +New Feature + - Add `add_stored` method to simplify adding entries without compression [#366](https://github.com/rubyzip/rubyzip/pull/366) Tooling / Documentation diff --git a/README.md b/README.md index 2ff41ed9..51b275b9 100644 --- a/README.md +++ b/README.md @@ -265,7 +265,13 @@ Zip.warn_invalid_date = false ### Size Validation -By default, `rubyzip`'s `extract` method checks that an entry's reported uncompressed size is not (significantly) smaller than its actual size. This is to help you protect your application against [zip bombs](https://en.wikipedia.org/wiki/Zip_bomb). Before `extract`ing an entry, you should check that its size is in the range you expect. For example, if your application supports processing up to 100 files at once, each up to 10MiB, your zip extraction code might look like: +**This setting defaults to `false` in rubyzip 1.3 for backward compatibility, but it will default to `true` in rubyzip 2.0.** + +If you set +``` +Zip.validate_entry_sizes = true +``` +then `rubyzip`'s `extract` method checks that an entry's reported uncompressed size is not (significantly) smaller than its actual size. This is to help you protect your application against [zip bombs](https://en.wikipedia.org/wiki/Zip_bomb). Before `extract`ing an entry, you should check that its size is in the range you expect. For example, if your application supports processing up to 100 files at once, each up to 10MiB, your zip extraction code might look like: ```ruby MAX_FILE_SIZE = 10 * 1024**2 # 10MiB diff --git a/lib/zip.rb b/lib/zip.rb index c3a6ed5e..eeac96a0 100644 --- a/lib/zip.rb +++ b/lib/zip.rb @@ -55,7 +55,7 @@ def reset! @write_zip64_support = false @warn_invalid_date = true @case_insensitive_match = false - @validate_entry_sizes = true + @validate_entry_sizes = false end def setup From 97cb6aefe6d12bd2429d7a2e119ccb26f259d71d Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Wed, 18 Sep 2019 18:34:23 +0100 Subject: [PATCH 021/469] Warn when an entry size is invalid --- lib/zip/entry.rb | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index bd3e4f34..677e49ef 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -604,14 +604,19 @@ def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exi set_extra_attributes_on_path(dest_path) bytes_written = 0 + warned = false buf = ''.dup while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf)) os << buf - - next unless ::Zip.validate_entry_sizes bytes_written += buf.bytesize - if bytes_written > size - raise ::Zip::EntrySizeError, "Entry #{name} should be #{size}B but is larger when inflated" + if bytes_written > size && !warned + message = "Entry #{name} should be #{size}B but is larger when inflated" + if ::Zip.validate_entry_sizes + raise ::Zip::EntrySizeError, message + else + puts "WARNING: #{message}" + warned = true + end end end end From 74d4bec371158c4c2a9fe965302dc9649c941a73 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Wed, 18 Sep 2019 21:04:33 +0100 Subject: [PATCH 022/469] Remove test files from gem --- rubyzip.gemspec | 1 - 1 file changed, 1 deletion(-) diff --git a/rubyzip.gemspec b/rubyzip.gemspec index 6b873752..8415d87c 100644 --- a/rubyzip.gemspec +++ b/rubyzip.gemspec @@ -13,7 +13,6 @@ Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.summary = 'rubyzip is a ruby module for reading and writing zip files' s.files = Dir.glob('{samples,lib}/**/*.rb') + %w[README.md TODO Rakefile] - s.test_files = Dir.glob('test/**/*') s.require_paths = ['lib'] s.license = 'BSD 2-Clause' s.metadata = { From 35446f467b739d05790356ab86915de76f0120f1 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Wed, 18 Sep 2019 21:47:09 +0100 Subject: [PATCH 023/469] Drop old ruby and JDK versions from CI --- .travis.yml | 12 +++--------- README.md | 2 +- rubyzip.gemspec | 2 +- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 358e2a8a..5b299c64 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,22 +2,16 @@ language: ruby dist: trusty cache: bundler rvm: - - 2.0 - - 2.1 - - 2.2 - - 2.3 - 2.4 - 2.5 - 2.6 - ruby-head matrix: include: - - rvm: jruby - jdk: oraclejdk8 - - rvm: jruby-9.1 - jdk: openjdk7 + - rvm: jruby-9.2 + jdk: openjdk8 - rvm: jruby-head - jdk: oraclejdk8 + jdk: openjdk8 - rvm: rbx-4 allow_failures: - rvm: ruby-head diff --git a/README.md b/README.md index 8255cd90..19201881 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ gem 'zip-zip' # will load compatibility for old rubyzip API. ## Requirements -- Ruby 1.9.2 or greater +- Ruby 2.4 or greater (for rubyzip 2.0; use 1.x for older rubies) ## Installation diff --git a/rubyzip.gemspec b/rubyzip.gemspec index 6b873752..e8f42384 100644 --- a/rubyzip.gemspec +++ b/rubyzip.gemspec @@ -23,7 +23,7 @@ Gem::Specification.new do |s| 'source_code_uri' => "https://github.com/rubyzip/rubyzip/tree/v#{s.version}", 'wiki_uri' => 'https://github.com/rubyzip/rubyzip/wiki' } - s.required_ruby_version = '>= 1.9.2' + s.required_ruby_version = '>= 2.4' s.add_development_dependency 'rake', '~> 10.3' s.add_development_dependency 'pry', '~> 0.10' s.add_development_dependency 'minitest', '~> 5.4' From 7c65e1e3595031392f1050b81fb2b95b0f2ee764 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Wed, 25 Sep 2019 19:58:16 +0100 Subject: [PATCH 024/469] Bump version to 1.3.0 --- Changelog.md | 2 +- lib/zip/version.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Changelog.md b/Changelog.md index 45f14333..36ae1009 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,7 +2,7 @@ - -# 1.3.0 (Next) +# 1.3.0 (2019-09-25) Security diff --git a/lib/zip/version.rb b/lib/zip/version.rb index afbccee8..37fba090 100644 --- a/lib/zip/version.rb +++ b/lib/zip/version.rb @@ -1,3 +1,3 @@ module Zip - VERSION = '1.2.4' + VERSION = '1.3.0' end From cb407b106541c345329a017d6eb34026cb372872 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Wed, 25 Sep 2019 20:56:53 +0100 Subject: [PATCH 025/469] Bump version to 2.0.0 --- Changelog.md | 12 ++++++++++++ README.md | 8 +------- lib/zip.rb | 2 +- lib/zip/version.rb | 2 +- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/Changelog.md b/Changelog.md index 36ae1009..90a6e085 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,18 @@ - +# 2.0.0 (2019-09-25) + +Security + +- Default the `validate_entry_sizes` option to `true`, so that callers can trust an entry's reported size when using `extract` [#403](https://github.com/rubyzip/rubyzip/pull/403) + - This option defaulted to `false` in 1.3.0 for backward compatibility, but it now defaults to `true`. If you are using an older version of ruby and can't yet upgrade to 2.x, you can still use 1.3.0 and set the option to `true`. + +Tooling / Documentation + +- Remove test files from the gem to avoid problems with antivirus detections on the test files [#405](https://github.com/rubyzip/rubyzip/pull/405) / [#384](https://github.com/rubyzip/rubyzip/issues/384) +- Drop support for unsupported ruby versions [#406](https://github.com/rubyzip/rubyzip/pull/406) + # 1.3.0 (2019-09-25) Security diff --git a/README.md b/README.md index 1f8531ca..059f22d1 100644 --- a/README.md +++ b/README.md @@ -265,13 +265,7 @@ Zip.warn_invalid_date = false ### Size Validation -**This setting defaults to `false` in rubyzip 1.3 for backward compatibility, but it will default to `true` in rubyzip 2.0.** - -If you set -``` -Zip.validate_entry_sizes = true -``` -then `rubyzip`'s `extract` method checks that an entry's reported uncompressed size is not (significantly) smaller than its actual size. This is to help you protect your application against [zip bombs](https://en.wikipedia.org/wiki/Zip_bomb). Before `extract`ing an entry, you should check that its size is in the range you expect. For example, if your application supports processing up to 100 files at once, each up to 10MiB, your zip extraction code might look like: +By default (in rubyzip >= 2.0), rubyzip's `extract` method checks that an entry's reported uncompressed size is not (significantly) smaller than its actual size. This is to help you protect your application against [zip bombs](https://en.wikipedia.org/wiki/Zip_bomb). Before `extract`ing an entry, you should check that its size is in the range you expect. For example, if your application supports processing up to 100 files at once, each up to 10MiB, your zip extraction code might look like: ```ruby MAX_FILE_SIZE = 10 * 1024**2 # 10MiB diff --git a/lib/zip.rb b/lib/zip.rb index eeac96a0..c3a6ed5e 100644 --- a/lib/zip.rb +++ b/lib/zip.rb @@ -55,7 +55,7 @@ def reset! @write_zip64_support = false @warn_invalid_date = true @case_insensitive_match = false - @validate_entry_sizes = false + @validate_entry_sizes = true end def setup diff --git a/lib/zip/version.rb b/lib/zip/version.rb index 37fba090..eb9cfa9b 100644 --- a/lib/zip/version.rb +++ b/lib/zip/version.rb @@ -1,3 +1,3 @@ module Zip - VERSION = '1.3.0' + VERSION = '2.0.0' end From 506d557edc29144c6f1e3110b4b99043232c3eaf Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Fri, 27 Sep 2019 16:13:56 +0100 Subject: [PATCH 026/469] StreamableStream now uses the OS temp directory. Rather than using the local folder. Fixes #410 --- lib/zip/streamable_stream.rb | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/zip/streamable_stream.rb b/lib/zip/streamable_stream.rb index 2a4bf507..642ddae2 100644 --- a/lib/zip/streamable_stream.rb +++ b/lib/zip/streamable_stream.rb @@ -2,12 +2,7 @@ module Zip class StreamableStream < DelegateClass(Entry) # nodoc:all def initialize(entry) super(entry) - dirname = if zipfile.is_a?(::String) - ::File.dirname(zipfile) - else - nil - end - @temp_file = Tempfile.new(::File.basename(name), dirname) + @temp_file = Tempfile.new(::File.basename(name)) @temp_file.binmode end From e87184200ab92220876577d312ce78354adf6bbf Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Fri, 27 Sep 2019 16:28:02 +0100 Subject: [PATCH 027/469] Require 'tmpdir' only in the tests. It's not used in the library code. --- lib/zip.rb | 1 - test/test_helper.rb | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zip.rb b/lib/zip.rb index c3a6ed5e..fa382376 100644 --- a/lib/zip.rb +++ b/lib/zip.rb @@ -1,7 +1,6 @@ require 'delegate' require 'singleton' require 'tempfile' -require 'tmpdir' require 'fileutils' require 'stringio' require 'zlib' diff --git a/test/test_helper.rb b/test/test_helper.rb index ddeba58b..6d11af6c 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -2,6 +2,7 @@ require 'minitest/autorun' require 'minitest/unit' require 'fileutils' +require 'tmpdir' require 'digest/sha1' require 'zip' require 'gentestfiles' From 34d2074ecbf6ef8557c29efc03a585c309be5c4b Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Fri, 4 Oct 2019 21:36:31 +0100 Subject: [PATCH 028/469] Update changelog for #411 --- Changelog.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 90a6e085..f8bff465 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,7 @@ # X.X.X (Next) -- +- Create temporary files in the system temporary directory instead of the directory of the zip file [#411](https://github.com/rubyzip/rubyzip/pull/411) +- Drop unused `tmpdir` requirement [#411](https://github.com/rubyzip/rubyzip/pull/411) # 2.0.0 (2019-09-25) From 340379f0808e9cc18df0248aeec07bdb95016faa Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Fri, 11 Oct 2019 19:31:42 +0100 Subject: [PATCH 029/469] Use `warn` instead of `puts` for messages from the library code. `warn` has the advantage of easily being disabled by, the `-W0` interpreter flag or setting $VERBOSE to nil. --- lib/zip/entry.rb | 6 +++--- lib/zip/extra_field/generic.rb | 2 +- lib/zip/input_stream.rb | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 677e49ef..e54ad012 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -163,7 +163,7 @@ def next_header_offset #:nodoc:all # is passed. def extract(dest_path = nil, &block) if dest_path.nil? && !name_safe? - puts "WARNING: skipped #{@name} as unsafe" + warn "WARNING: skipped #{@name} as unsafe" return self end @@ -614,7 +614,7 @@ def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exi if ::Zip.validate_entry_sizes raise ::Zip::EntrySizeError, message else - puts "WARNING: #{message}" + warn "WARNING: #{message}" warned = true end end @@ -642,7 +642,7 @@ def create_directory(dest_path) def create_symlink(dest_path) # TODO: Symlinks pose security challenges. Symlink support temporarily # removed in view of https://github.com/rubyzip/rubyzip/issues/369 . - puts "WARNING: skipped symlink #{dest_path}" + warn "WARNING: skipped symlink #{dest_path}" end # apply missing data from the zip64 extra information field, if present diff --git a/lib/zip/extra_field/generic.rb b/lib/zip/extra_field/generic.rb index 5931b5c2..c5398ee3 100644 --- a/lib/zip/extra_field/generic.rb +++ b/lib/zip/extra_field/generic.rb @@ -16,7 +16,7 @@ def initial_parse(binstr) # If nil, start with empty. return false elsif binstr[0, 2] != self.class.const_get(:HEADER_ID) - $stderr.puts 'Warning: weired extra feild header ID. skip parsing' + warn 'Warning: weired extra feild header ID. skip parsing' return false end [binstr[2, 2].unpack('v')[0], binstr[4..-1]] diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index 95fc3c16..b9c35111 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -103,7 +103,7 @@ def open(filename_or_io, offset = 0, decrypter = nil) end def open_buffer(filename_or_io, offset = 0) - puts 'open_buffer is deprecated!!! Use open instead!' + warn 'open_buffer is deprecated!!! Use open instead!' open(filename_or_io, offset) end end From 935a4f31a2fc781b2c8b41cadb42ab9fd85d2a6e Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 12 Oct 2019 07:26:15 +0100 Subject: [PATCH 030/469] Make warning messages consistent. And fix a few spelling mistakes. --- lib/zip/entry.rb | 8 ++++---- lib/zip/extra_field/generic.rb | 2 +- test/file_extract_test.rb | 2 +- test/settings_test.rb | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index e54ad012..142308a1 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -163,7 +163,7 @@ def next_header_offset #:nodoc:all # is passed. def extract(dest_path = nil, &block) if dest_path.nil? && !name_safe? - warn "WARNING: skipped #{@name} as unsafe" + warn "WARNING: skipped '#{@name}' as unsafe." return self end @@ -591,7 +591,7 @@ def clean_up def set_time(binary_dos_date, binary_dos_time) @time = ::Zip::DOSTime.parse_binary_dos_format(binary_dos_date, binary_dos_time) rescue ArgumentError - warn 'Invalid date/time in zip entry' if ::Zip.warn_invalid_date + warn 'WARNING: invalid date/time in zip entry.' if ::Zip.warn_invalid_date end def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exists_proc }) @@ -610,7 +610,7 @@ def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exi os << buf bytes_written += buf.bytesize if bytes_written > size && !warned - message = "Entry #{name} should be #{size}B but is larger when inflated" + message = "entry '#{name}' should be #{size}B, but is larger when inflated." if ::Zip.validate_entry_sizes raise ::Zip::EntrySizeError, message else @@ -642,7 +642,7 @@ def create_directory(dest_path) def create_symlink(dest_path) # TODO: Symlinks pose security challenges. Symlink support temporarily # removed in view of https://github.com/rubyzip/rubyzip/issues/369 . - warn "WARNING: skipped symlink #{dest_path}" + warn "WARNING: skipped symlink '#{dest_path}'." end # apply missing data from the zip64 extra information field, if present diff --git a/lib/zip/extra_field/generic.rb b/lib/zip/extra_field/generic.rb index c5398ee3..d61137fe 100644 --- a/lib/zip/extra_field/generic.rb +++ b/lib/zip/extra_field/generic.rb @@ -16,7 +16,7 @@ def initial_parse(binstr) # If nil, start with empty. return false elsif binstr[0, 2] != self.class.const_get(:HEADER_ID) - warn 'Warning: weired extra feild header ID. skip parsing' + warn 'WARNING: weird extra field header ID. Skip parsing it.' return false end [binstr[2, 2].unpack('v')[0], binstr[4..-1]] diff --git a/test/file_extract_test.rb b/test/file_extract_test.rb index 6103aeae..49a77099 100644 --- a/test/file_extract_test.rb +++ b/test/file_extract_test.rb @@ -136,7 +136,7 @@ def test_extract_incorrect_size a_entry.extract end assert_equal \ - 'Entry a should be 1B but is larger when inflated', + "entry 'a' should be 1B, but is larger when inflated.", error.message end end diff --git a/test/settings_test.rb b/test/settings_test.rb index c2c9cce1..7c1331a6 100644 --- a/test/settings_test.rb +++ b/test/settings_test.rb @@ -80,7 +80,7 @@ def test_true_warn_invalid_date test_file = File.join(File.dirname(__FILE__), 'data', 'WarnInvalidDate.zip') Zip.warn_invalid_date = true - assert_output('', /Invalid date\/time in zip entry/) do + assert_output('', /invalid date\/time in zip entry/) do ::Zip::File.open(test_file) do |_zf| end end From ccabd94e42dd1f29cf0d4f9897926aadd854e98b Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 13 Oct 2019 15:40:49 +0100 Subject: [PATCH 031/469] Update tests to check error messages. Check that they say the right things and are on stderr, not stdout. A nice side effect of this is that it cleans up the test output. --- test/file_extract_test.rb | 4 +- test/path_traversal_test.rb | 91 +++++++++++++++++++++++++------------ 2 files changed, 65 insertions(+), 30 deletions(-) diff --git a/test/file_extract_test.rb b/test/file_extract_test.rb index 49a77099..a494f781 100644 --- a/test/file_extract_test.rb +++ b/test/file_extract_test.rb @@ -127,7 +127,9 @@ def test_extract_incorrect_size assert_equal fake_size, a_entry.size ::Zip.validate_entry_sizes = false - a_entry.extract + assert_output('', /.+\'a\'.+1B.+/) do + a_entry.extract + end assert_equal true_size, File.size(file_name) FileUtils.rm file_name diff --git a/test/path_traversal_test.rb b/test/path_traversal_test.rb index e5bdd722..8b6f67d5 100644 --- a/test/path_traversal_test.rb +++ b/test/path_traversal_test.rb @@ -1,3 +1,5 @@ +require 'test_helper' + class PathTraversalTest < MiniTest::Test TEST_FILE_ROOT = File.absolute_path('test/data/path_traversal') @@ -8,10 +10,18 @@ def setup FileUtils.rm_f '/tmp/file.txt' end - def extract_path_traversal_zip(name) - Zip::File.open(File.join(TEST_FILE_ROOT, name)) do |zip_file| - zip_file.each do |entry| - entry.extract + def extract_paths(zip_path, entries) + ::Zip::File.open(::File.join(TEST_FILE_ROOT, zip_path)) do |zip| + entries.each do |entry, test| + if test == :error + assert_raises(Errno::ENOENT) do + zip.find_entry(entry).extract + end + else + assert_output('', test) do + zip.find_entry(entry).extract + end + end end end end @@ -27,65 +37,79 @@ def in_tmpdir end def test_leading_slash + entries = { '/tmp/moo' => /WARNING: skipped \'\/tmp\/moo\'/ } in_tmpdir do - extract_path_traversal_zip 'jwilk/absolute1.zip' + extract_paths(['jwilk', 'absolute1.zip'], entries) refute File.exist?('/tmp/moo') end end def test_multiple_leading_slashes + entries = { '//tmp/moo' => /WARNING: skipped \'\/\/tmp\/moo\'/ } in_tmpdir do - extract_path_traversal_zip 'jwilk/absolute2.zip' + extract_paths(['jwilk', 'absolute2.zip'], entries) refute File.exist?('/tmp/moo') end end def test_leading_dot_dot + entries = { '../moo' => /WARNING: skipped \'\.\.\/moo\'/ } in_tmpdir do - extract_path_traversal_zip 'jwilk/relative0.zip' + extract_paths(['jwilk', 'relative0.zip'], entries) refute File.exist?('../moo') end end def test_non_leading_dot_dot_with_existing_folder + entries = { + 'tmp/' => '', + 'tmp/../../moo' => /WARNING: skipped \'tmp\/\.\.\/\.\.\/moo\'/ + } in_tmpdir do - extract_path_traversal_zip 'relative1.zip' + extract_paths('relative1.zip', entries) assert Dir.exist?('tmp') refute File.exist?('../moo') end end def test_non_leading_dot_dot_without_existing_folder + entries = { 'tmp/../../moo' => /WARNING: skipped \'tmp\/\.\.\/\.\.\/moo\'/ } in_tmpdir do - extract_path_traversal_zip 'jwilk/relative2.zip' + extract_paths(['jwilk', 'relative2.zip'], entries) refute File.exist?('../moo') end end def test_file_symlink + entries = { 'moo' => '' } in_tmpdir do - extract_path_traversal_zip 'jwilk/symlink.zip' + extract_paths(['jwilk', 'symlink.zip'], entries) assert File.exist?('moo') refute File.exist?('/tmp/moo') end end def test_directory_symlink + # Can't create tmp/moo, because the tmp symlink is skipped. + entries = { + 'tmp' => /WARNING: skipped symlink \'tmp\'/, + 'tmp/moo' => :error + } in_tmpdir do - # Can't create tmp/moo, because the tmp symlink is skipped. - assert_raises Errno::ENOENT do - extract_path_traversal_zip 'jwilk/dirsymlink.zip' - end + extract_paths(['jwilk', 'dirsymlink.zip'], entries) refute File.exist?('/tmp/moo') end end def test_two_directory_symlinks_a + # Can't create par/moo because the symlinks are skipped. + entries = { + 'cur' => /WARNING: skipped symlink \'cur\'/, + 'par' => /WARNING: skipped symlink \'par\'/, + 'par/moo' => :error + } in_tmpdir do - # Can't create par/moo because the symlinks are skipped. - assert_raises Errno::ENOENT do - extract_path_traversal_zip 'jwilk/dirsymlink2a.zip' - end + extract_paths(['jwilk', 'dirsymlink2a.zip'], entries) refute File.exist?('cur') refute File.exist?('par') refute File.exist?('par/moo') @@ -93,26 +117,33 @@ def test_two_directory_symlinks_a end def test_two_directory_symlinks_b + # Can't create par/moo, because the symlinks are skipped. + entries = { + 'cur' => /WARNING: skipped symlink \'cur\'/, + 'cur/par' => /WARNING: skipped symlink \'cur\/par\'/, + 'par/moo' => :error + } in_tmpdir do - # Can't create par/moo, because the symlinks are skipped. - assert_raises Errno::ENOENT do - extract_path_traversal_zip 'jwilk/dirsymlink2b.zip' - end + extract_paths(['jwilk', 'dirsymlink2b.zip'], entries) refute File.exist?('cur') refute File.exist?('../moo') end end def test_entry_name_with_absolute_path_does_not_extract + entries = { + '/tmp/' => /WARNING: skipped \'\/tmp\/\'/, + '/tmp/file.txt' => /WARNING: skipped \'\/tmp\/file.txt\'/ + } in_tmpdir do - extract_path_traversal_zip 'tuzovakaoff/absolutepath.zip' + extract_paths(['tuzovakaoff', 'absolutepath.zip'], entries) refute File.exist?('/tmp/file.txt') end end def test_entry_name_with_absolute_path_extract_when_given_different_path in_tmpdir do |test_path| - zip_path = File.join(TEST_FILE_ROOT, 'tuzovakaoff/absolutepath.zip') + zip_path = File.join(TEST_FILE_ROOT, 'tuzovakaoff', 'absolutepath.zip') Zip::File.open(zip_path) do |zip_file| zip_file.each do |entry| entry.extract(File.join(test_path, entry.name)) @@ -123,18 +154,20 @@ def test_entry_name_with_absolute_path_extract_when_given_different_path end def test_entry_name_with_relative_symlink + # Doesn't create the symlink path, so can't create path/file.txt. + entries = { + 'path' => /WARNING: skipped symlink \'path\'/, + 'path/file.txt' => :error + } in_tmpdir do - # Doesn't create the symlink path, so can't create path/file.txt. - assert_raises Errno::ENOENT do - extract_path_traversal_zip 'tuzovakaoff/symlink.zip' - end + extract_paths(['tuzovakaoff', 'symlink.zip'], entries) refute File.exist?('/tmp/file.txt') end end def test_entry_name_with_tilde in_tmpdir do - extract_path_traversal_zip 'tilde.zip' + extract_paths('tilde.zip', '~tilde~' => '') assert File.exist?('~tilde~') end end From f58e38012cf4d7493f70ffdc4e9a583d94f72ad5 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 19 Oct 2019 07:04:04 +0100 Subject: [PATCH 032/469] Add the `options` parameter to `Zip::File.open`. --- lib/zip/file.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 9c7f3cbd..b6eec375 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -107,8 +107,8 @@ class << self # Same as #new. If a block is passed the ZipFile object is passed # to the block and is automatically closed afterwards just as with # ruby's builtin File.open method. - def open(file_name, create = false) - zf = ::Zip::File.new(file_name, create) + def open(file_name, create = false, options = {}) + zf = ::Zip::File.new(file_name, create, false, options) return zf unless block_given? begin yield zf From 3fea9958c2da24d16b20f545d982ad0c0a3d7f57 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 19 Oct 2019 07:15:51 +0100 Subject: [PATCH 033/469] Update comment for `Zip::File.open`. --- lib/zip/file.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index b6eec375..7c051bcd 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -104,9 +104,9 @@ def initialize(path_or_io, create = false, buffer = false, options = {}) end class << self - # Same as #new. If a block is passed the ZipFile object is passed - # to the block and is automatically closed afterwards just as with - # ruby's builtin File.open method. + # Similar to ::new. If a block is passed the Zip::File object is passed + # to the block and is automatically closed afterwards, just as with + # ruby's builtin File::open method. def open(file_name, create = false, options = {}) zf = ::Zip::File.new(file_name, create, false, options) return zf unless block_given? From e43e36057cd11ced462dd6918dff99f2ce1463fd Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Sat, 19 Oct 2019 18:20:15 +0100 Subject: [PATCH 034/469] Update changelog for #416 and #418 --- Changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changelog.md b/Changelog.md index f8bff465..85efaddb 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,7 @@ # X.X.X (Next) +- Allow `Zip::File.open` to take an options hash like `Zip::File.new` [#418](https://github.com/rubyzip/rubyzip/pull/418) +- Always print warnings with `warn`, instead of a mix of `puts` and `warn` [#416](https://github.com/rubyzip/rubyzip/pull/416) - Create temporary files in the system temporary directory instead of the directory of the zip file [#411](https://github.com/rubyzip/rubyzip/pull/411) - Drop unused `tmpdir` requirement [#411](https://github.com/rubyzip/rubyzip/pull/411) From 253457545ea6bff299beb4ceec992013c41d8d19 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 16 Sep 2019 16:25:14 +0100 Subject: [PATCH 035/469] Correctly set/default options in the File class. Fixes #395. Set the options to false for now for consistency. --- lib/zip/file.rb | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 7c051bcd..e494a653 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -51,6 +51,12 @@ class File < CentralDirectory DATA_BUFFER_SIZE = 8192 IO_METHODS = [:tell, :seek, :read, :close] + DEFAULT_OPTIONS = { + restore_ownership: false, + restore_permissions: false, + restore_times: false + }.freeze + attr_reader :name # default -> false @@ -66,6 +72,7 @@ class File < CentralDirectory # a new archive if it doesn't exist already. def initialize(path_or_io, create = false, buffer = false, options = {}) super() + options = DEFAULT_OPTIONS.merge(options) @name = path_or_io.respond_to?(:path) ? path_or_io.path : path_or_io @comment = '' @create = create ? true : false # allow any truthy value to mean true @@ -98,9 +105,9 @@ def initialize(path_or_io, create = false, buffer = false, options = {}) @stored_entries = @entry_set.dup @stored_comment = @comment - @restore_ownership = options[:restore_ownership] || false - @restore_permissions = options[:restore_permissions] || true - @restore_times = options[:restore_times] || true + @restore_ownership = options[:restore_ownership] + @restore_permissions = options[:restore_permissions] + @restore_times = options[:restore_times] end class << self From 378293539d84916f36f7a8793ab3c1d801d9c33d Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 16 Sep 2019 16:26:40 +0100 Subject: [PATCH 036/469] Make the attr_accessors in File more readable. Note what the default is and that a couple of them will change at some point soon. --- lib/zip/file.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index e494a653..df0594ce 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -59,12 +59,15 @@ class File < CentralDirectory attr_reader :name - # default -> false + # default -> false. attr_accessor :restore_ownership - # default -> false + + # default -> false, but will be set to true in a future version. attr_accessor :restore_permissions - # default -> true + + # default -> false, but will be set to true in a future version. attr_accessor :restore_times + # Returns the zip files comment, if it has one attr_accessor :comment From 8c694d38ee683b1c2fda3430a928e2308942e434 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 6 Oct 2019 19:37:03 +0100 Subject: [PATCH 037/469] Add functionality to restore file timestamps. There has been an option in `Zip::File` (`:restore_times`) for a long time, but it seems it has never worked. Firstly the actual timestamp of an added file wasn't being saved, and secondly an extracted file wasn't having its timestamp set correctly. This commit fixes both of those issues, and adds tests to make sure. --- lib/zip/entry.rb | 18 ++++++++---- test/file_options_test.rb | 58 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 test/file_options_test.rb diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 142308a1..88b50494 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -406,16 +406,22 @@ def get_extra_attributes_from_path(path) # :nodoc: @unix_uid = stat.uid @unix_gid = stat.gid @unix_perms = stat.mode & 0o7777 + + mtime = stat.mtime + @time = ::Zip::DOSTime.local(mtime.year, mtime.month, mtime.day, mtime.hour, mtime.min, mtime.sec) end - def set_unix_permissions_on_path(dest_path) - # BUG: does not update timestamps into account + def set_unix_attributes_on_path(dest_path) # ignore setuid/setgid bits by default. honor if @restore_ownership unix_perms_mask = 0o1777 unix_perms_mask = 0o7777 if @restore_ownership ::FileUtils.chmod(@unix_perms & unix_perms_mask, dest_path) if @restore_permissions && @unix_perms ::FileUtils.chown(@unix_uid, @unix_gid, dest_path) if @restore_ownership && @unix_uid && @unix_gid && ::Process.egid == 0 - # File::utimes() + + # Restore the timestamp on a file. This will either have come from the + # original source file that was copied into the archive, or from the + # creation date of the archive if there was no original source file. + ::FileUtils.touch(dest_path, mtime: time) if @restore_times end def set_extra_attributes_on_path(dest_path) # :nodoc: @@ -423,7 +429,7 @@ def set_extra_attributes_on_path(dest_path) # :nodoc: case @fstype when ::Zip::FSTYPE_UNIX - set_unix_permissions_on_path(dest_path) + set_unix_attributes_on_path(dest_path) end end @@ -601,8 +607,6 @@ def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exi end ::File.open(dest_path, 'wb') do |os| get_input_stream do |is| - set_extra_attributes_on_path(dest_path) - bytes_written = 0 warned = false buf = ''.dup @@ -621,6 +625,8 @@ def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exi end end end + + set_extra_attributes_on_path(dest_path) end def create_directory(dest_path) diff --git a/test/file_options_test.rb b/test/file_options_test.rb new file mode 100644 index 00000000..4f8e5717 --- /dev/null +++ b/test/file_options_test.rb @@ -0,0 +1,58 @@ +require 'test_helper' + +class FileOptionsTest < MiniTest::Test + ZIPPATH = ::File.join(Dir.tmpdir, 'options.zip').freeze + TXTPATH = ::File.expand_path(::File.join('data', 'file1.txt'), __dir__).freeze + EXTPATH_1 = ::File.join(Dir.tmpdir, 'extracted_1.txt').freeze + EXTPATH_2 = ::File.join(Dir.tmpdir, 'extracted_2.txt').freeze + ENTRY_1 = 'entry_1.txt'.freeze + ENTRY_2 = 'entry_2.txt'.freeze + + def teardown + ::File.unlink(ZIPPATH) if ::File.exist?(ZIPPATH) + ::File.unlink(EXTPATH_1) if ::File.exist?(EXTPATH_1) + ::File.unlink(EXTPATH_2) if ::File.exist?(EXTPATH_2) + end + + def test_restore_times_true + ::Zip::File.open(ZIPPATH, true) do |zip| + zip.add(ENTRY_1, TXTPATH) + zip.add_stored(ENTRY_2, TXTPATH) + end + + ::Zip::File.open(ZIPPATH, false, restore_times: true) do |zip| + zip.extract(ENTRY_1, EXTPATH_1) + zip.extract(ENTRY_2, EXTPATH_2) + end + + assert_time_equal(::File.mtime(TXTPATH), ::File.mtime(EXTPATH_1)) + assert_time_equal(::File.mtime(TXTPATH), ::File.mtime(EXTPATH_2)) + end + + def test_restore_times_false + ::Zip::File.open(ZIPPATH, true) do |zip| + zip.add(ENTRY_1, TXTPATH) + zip.add_stored(ENTRY_2, TXTPATH) + end + + ::Zip::File.open(ZIPPATH, false, restore_times: false) do |zip| + zip.extract(ENTRY_1, EXTPATH_1) + zip.extract(ENTRY_2, EXTPATH_2) + end + + assert_time_equal(::Time.now, ::File.mtime(EXTPATH_1)) + assert_time_equal(::Time.now, ::File.mtime(EXTPATH_2)) + end + + private + + # Method to compare file times. DOS times only have 2 second accuracy. + def assert_time_equal(expected, actual) + assert_equal(expected.year, actual.year) + assert_equal(expected.month, actual.month) + assert_equal(expected.day, actual.day) + assert_equal(expected.hour, actual.hour) + assert_equal(expected.min, actual.min) + assert_in_delta(expected.sec, actual.sec, 1) + end +end From 2bdd37d8949aadb8b07a598f720e0d96b571d5a2 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 6 Oct 2019 20:01:14 +0100 Subject: [PATCH 038/469] Add a convenience method for creating DOSTime instances. DOSTime::from_time creates a DOSTime instance from a vanilla Time instance. --- lib/zip/dos_time.rb | 5 +++++ lib/zip/entry.rb | 4 +--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/zip/dos_time.rb b/lib/zip/dos_time.rb index bf0cb7e0..c912b773 100644 --- a/lib/zip/dos_time.rb +++ b/lib/zip/dos_time.rb @@ -29,6 +29,11 @@ def dos_equals(other) to_i / 2 == other.to_i / 2 end + # Create a DOSTime instance from a vanilla Time instance. + def self.from_time(time) + local(time.year, time.month, time.day, time.hour, time.min, time.sec) + end + def self.parse_binary_dos_format(binaryDosDate, binaryDosTime) second = 2 * (0b11111 & binaryDosTime) minute = (0b11111100000 & binaryDosTime) >> 5 diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 88b50494..37b1690b 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -406,9 +406,7 @@ def get_extra_attributes_from_path(path) # :nodoc: @unix_uid = stat.uid @unix_gid = stat.gid @unix_perms = stat.mode & 0o7777 - - mtime = stat.mtime - @time = ::Zip::DOSTime.local(mtime.year, mtime.month, mtime.day, mtime.hour, mtime.min, mtime.sec) + @time = ::Zip::DOSTime.from_time(stat.mtime) end def set_unix_attributes_on_path(dest_path) From 1a21f39e82323e023df882f94b408da55d54c4c6 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 6 Oct 2019 23:08:52 +0100 Subject: [PATCH 039/469] Add a test for restoring file permissions on extract. --- test/file_options_test.rb | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/file_options_test.rb b/test/file_options_test.rb index 4f8e5717..48b83601 100644 --- a/test/file_options_test.rb +++ b/test/file_options_test.rb @@ -3,15 +3,46 @@ class FileOptionsTest < MiniTest::Test ZIPPATH = ::File.join(Dir.tmpdir, 'options.zip').freeze TXTPATH = ::File.expand_path(::File.join('data', 'file1.txt'), __dir__).freeze + TXTPATH_600 = ::File.join(Dir.tmpdir, 'file1.600.txt').freeze + TXTPATH_755 = ::File.join(Dir.tmpdir, 'file1.755.txt').freeze EXTPATH_1 = ::File.join(Dir.tmpdir, 'extracted_1.txt').freeze EXTPATH_2 = ::File.join(Dir.tmpdir, 'extracted_2.txt').freeze + EXTPATH_3 = ::File.join(Dir.tmpdir, 'extracted_3.txt').freeze ENTRY_1 = 'entry_1.txt'.freeze ENTRY_2 = 'entry_2.txt'.freeze + ENTRY_3 = 'entry_3.txt'.freeze def teardown ::File.unlink(ZIPPATH) if ::File.exist?(ZIPPATH) ::File.unlink(EXTPATH_1) if ::File.exist?(EXTPATH_1) ::File.unlink(EXTPATH_2) if ::File.exist?(EXTPATH_2) + ::File.unlink(EXTPATH_3) if ::File.exist?(EXTPATH_3) + ::File.unlink(TXTPATH_600) if ::File.exist?(TXTPATH_600) + ::File.unlink(TXTPATH_755) if ::File.exist?(TXTPATH_755) + end + + def test_restore_permissions + # Copy and set up files with different permissions. + ::FileUtils.cp(TXTPATH, TXTPATH_600) + ::File.chmod(0600, TXTPATH_600) + ::FileUtils.cp(TXTPATH, TXTPATH_755) + ::File.chmod(0755, TXTPATH_755) + + ::Zip::File.open(ZIPPATH, true) do |zip| + zip.add(ENTRY_1, TXTPATH) + zip.add(ENTRY_2, TXTPATH_600) + zip.add(ENTRY_3, TXTPATH_755) + end + + ::Zip::File.open(ZIPPATH, false, restore_permissions: true) do |zip| + zip.extract(ENTRY_1, EXTPATH_1) + zip.extract(ENTRY_2, EXTPATH_2) + zip.extract(ENTRY_3, EXTPATH_3) + end + + assert_equal(::File.stat(TXTPATH).mode, ::File.stat(EXTPATH_1).mode) + assert_equal(::File.stat(TXTPATH_600).mode, ::File.stat(EXTPATH_2).mode) + assert_equal(::File.stat(TXTPATH_755).mode, ::File.stat(EXTPATH_3).mode) end def test_restore_times_true From acc3d9ed2415b9c02b8fdedd352061372cd2dd14 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 21 Oct 2019 12:31:43 +0100 Subject: [PATCH 040/469] Use `fast-finish` in the test matrix. This means that long-running tests that are allowed to fail don't hold up the build being marked as finished, if all the required tests have passed. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 5b299c64..ac260edd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ rvm: - 2.6 - ruby-head matrix: + fast_finish: true include: - rvm: jruby-9.2 jdk: openjdk8 From 0fd4f914cda5da9cc38d58ecb39e9126f8b45712 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 21 Oct 2019 12:57:04 +0100 Subject: [PATCH 041/469] Update to use a later Ubuntu version on Travis. Move to Xenial, which allows us to test with later OpenJDKs. Moving to Bionic would be nice but it no longer has OpenJDK 8, which is still in support. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ac260edd..f22621de 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: ruby -dist: trusty +dist: xenial cache: bundler rvm: - 2.4 From 323437c5770cc2159c12b8224acc98e5a1301c3b Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 21 Oct 2019 14:18:57 +0100 Subject: [PATCH 042/469] Update to OpenJDK 11 (LTS) in the CI tests. OpenJDK 8 is now out of (commercial) support. Leave a version testing with OpenJDK 8 as it's still in free support for another year. --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f22621de..21a4c64f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,8 +11,10 @@ matrix: include: - rvm: jruby-9.2 jdk: openjdk8 + - rvm: jruby-9.2 + jdk: openjdk11 - rvm: jruby-head - jdk: openjdk8 + jdk: openjdk11 - rvm: rbx-4 allow_failures: - rvm: ruby-head From 6389d657c13601b1727d1e7d7fe969d0dbb0330c Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Sun, 27 Oct 2019 22:34:41 +0000 Subject: [PATCH 043/469] Update changelog for #413 and #419 --- Changelog.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Changelog.md b/Changelog.md index 85efaddb..e82fc68d 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,10 +1,17 @@ # X.X.X (Next) +- Fix (at least partially) the `restore_times` and `restore_permissions` options to `Zip::File.new` [#413](https://github.com/rubyzip/rubyzip/pull/413) + - Previously, neither option did anything, regardless of what it was set to. We have therefore defaulted them to `false` to preserve the current behavior, for the time being. If you have explicitly set either to `true`, it will now have an effect. + - Note that `restore_times` in this release does nothing on Windows and only restores `mtime`, not `atime` or `ctime`. - Allow `Zip::File.open` to take an options hash like `Zip::File.new` [#418](https://github.com/rubyzip/rubyzip/pull/418) - Always print warnings with `warn`, instead of a mix of `puts` and `warn` [#416](https://github.com/rubyzip/rubyzip/pull/416) - Create temporary files in the system temporary directory instead of the directory of the zip file [#411](https://github.com/rubyzip/rubyzip/pull/411) - Drop unused `tmpdir` requirement [#411](https://github.com/rubyzip/rubyzip/pull/411) +Tooling + +- Move CI to xenial and include jruby on JDK11 [#419](https://github.com/rubyzip/rubyzip/pull/419/files) + # 2.0.0 (2019-09-25) Security From cef3bc0784f9b1aaa2ca9ad437d0ec3b15cbe8a3 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Thu, 31 Oct 2019 17:27:19 +0000 Subject: [PATCH 044/469] A test to ensure find/get entry calls agree. --- test/file_options_test.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/file_options_test.rb b/test/file_options_test.rb index 48b83601..1a73e980 100644 --- a/test/file_options_test.rb +++ b/test/file_options_test.rb @@ -75,6 +75,24 @@ def test_restore_times_false assert_time_equal(::Time.now, ::File.mtime(EXTPATH_2)) end + def test_get_find_consistency + testzip = ::File.expand_path(::File.join('data', 'globTest.zip'), __dir__) + file_f = ::File.expand_path('f_test.txt', Dir.tmpdir) + file_g = ::File.expand_path('g_test.txt', Dir.tmpdir) + + ::Zip::File.open(testzip) do |zip| + e1 = zip.find_entry('globTest/food.txt') + e1.extract(file_f) + e2 = zip.get_entry('globTest/food.txt') + e2.extract(file_g) + end + + assert_time_equal(::File.mtime(file_f), ::File.mtime(file_g)) + ensure + ::File.unlink(file_f) + ::File.unlink(file_g) + end + private # Method to compare file times. DOS times only have 2 second accuracy. From 0f6ca04a9d5d2fbaf526e4570c80fc7c3113694e Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Thu, 31 Oct 2019 17:30:14 +0000 Subject: [PATCH 045/469] Set the default options in `Entry` the same as `File`. --- lib/zip/entry.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 37b1690b..f6d5cb5e 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -34,7 +34,7 @@ def set_default_vars_values end @follow_symlinks = false - @restore_times = true + @restore_times = false @restore_permissions = false @restore_ownership = false # BUG: need an extra field to support uid/gid's From 2d6b6e024b1093f19b7ea9da2b5f33eb2d32df9e Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Thu, 31 Oct 2019 18:12:18 +0000 Subject: [PATCH 046/469] Ensure File#get/find_entry work consistently. I have switched around the logic somewhat so that `get_entry` calls `find_entry` and raises an exception if it gets `nil` back. --- lib/zip/file.rb | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index df0594ce..e0c62443 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -376,7 +376,13 @@ def commit_required? # Searches for entry with the specified name. Returns nil if # no entry is found. See also get_entry def find_entry(entry_name) - @entry_set.find_entry(entry_name) + selected_entry = @entry_set.find_entry(entry_name) + return if selected_entry.nil? + + selected_entry.restore_ownership = @restore_ownership + selected_entry.restore_permissions = @restore_permissions + selected_entry.restore_times = @restore_times + selected_entry end # Searches for entries given a glob @@ -388,10 +394,8 @@ def glob(*args, &block) # if no entry is found. def get_entry(entry) selected_entry = find_entry(entry) - raise Errno::ENOENT, entry unless selected_entry - selected_entry.restore_ownership = @restore_ownership - selected_entry.restore_permissions = @restore_permissions - selected_entry.restore_times = @restore_times + raise Errno::ENOENT, entry if selected_entry.nil? + selected_entry end From f3a2f4a8ec2804ea520a010592d2daed065681e9 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Thu, 31 Oct 2019 18:15:17 +0000 Subject: [PATCH 047/469] Add tests for File#get/find_entry. --- test/file_test.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/file_test.rb b/test/file_test.rb index 94ff769c..d7f5cb8e 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -653,6 +653,21 @@ def test_open_xls_does_not_raise_type_error ::Zip::File.open('test/data/test.xls') end + def test_find_get_entry + ::Zip::File.open(TEST_ZIP.zip_name) do |zf| + assert_nil zf.find_entry('not_in_here.txt') + + refute_nil zf.find_entry('test/data/generated/empty.txt') + + assert_raises(Errno::ENOENT) do + zf.get_entry('not_in_here.txt') + end + + # Should not raise anything. + zf.get_entry('test/data/generated/empty.txt') + end + end + private def assert_contains(zf, entryName, filename = entryName) From d3027eab9556c7b803686a7256843b4a764ef116 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Sun, 15 Dec 2019 17:44:40 +0000 Subject: [PATCH 048/469] Update changelog for #423 --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index e82fc68d..63e01769 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,7 @@ - Fix (at least partially) the `restore_times` and `restore_permissions` options to `Zip::File.new` [#413](https://github.com/rubyzip/rubyzip/pull/413) - Previously, neither option did anything, regardless of what it was set to. We have therefore defaulted them to `false` to preserve the current behavior, for the time being. If you have explicitly set either to `true`, it will now have an effect. + - Previously, `Zip::File` did not pass the options to `Zip::Entry` in some cases. [#423](https://github.com/rubyzip/rubyzip/pull/423) - Note that `restore_times` in this release does nothing on Windows and only restores `mtime`, not `atime` or `ctime`. - Allow `Zip::File.open` to take an options hash like `Zip::File.new` [#418](https://github.com/rubyzip/rubyzip/pull/418) - Always print warnings with `warn`, instead of a mix of `puts` and `warn` [#416](https://github.com/rubyzip/rubyzip/pull/416) From b58b97fe23a0725f2de9943ad72634aed3d5073f Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 27 Oct 2019 10:29:27 +0000 Subject: [PATCH 049/469] UniversalTime: correctly set the flags. When a timestamp is set/unset the flags should reflect this. --- lib/zip/extra_field/universal_time.rb | 31 +++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/lib/zip/extra_field/universal_time.rb b/lib/zip/extra_field/universal_time.rb index 453b539d..39c292c4 100644 --- a/lib/zip/extra_field/universal_time.rb +++ b/lib/zip/extra_field/universal_time.rb @@ -4,24 +4,43 @@ class ExtraField::UniversalTime < ExtraField::Generic HEADER_ID = 'UT' register_map + ATIME_MASK = 0b010 + CTIME_MASK = 0b100 + MTIME_MASK = 0b001 + def initialize(binstr = nil) @ctime = nil @mtime = nil @atime = nil - @flag = nil + @flag = 0 binstr && merge(binstr) end - attr_accessor :atime, :ctime, :mtime, :flag + attr_reader :atime, :ctime, :mtime, :flag + + def atime=(time) + @flag = time.nil? ? @flag & ~ATIME_MASK : @flag | ATIME_MASK + @atime = time + end + + def ctime=(time) + @flag = time.nil? ? @flag & ~CTIME_MASK : @flag | CTIME_MASK + @ctime = time + end + + def mtime=(time) + @flag = time.nil? ? @flag & ~MTIME_MASK : @flag | MTIME_MASK + @mtime = time + end def merge(binstr) return if binstr.empty? size, content = initial_parse(binstr) size || return - @flag, mtime, atime, ctime = content.unpack('CVVV') - mtime && @mtime ||= ::Zip::DOSTime.at(mtime) - atime && @atime ||= ::Zip::DOSTime.at(atime) - ctime && @ctime ||= ::Zip::DOSTime.at(ctime) + @flag, mt, at, ct = content.unpack('CVVV') + mt && @mtime ||= ::Zip::DOSTime.at(mt) + at && @atime ||= ::Zip::DOSTime.at(at) + ct && @ctime ||= ::Zip::DOSTime.at(ct) end def ==(other) From 65cfd8a9a5982c5fa1dd059e2c688d1242041c19 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 27 Oct 2019 16:52:43 +0000 Subject: [PATCH 050/469] UniversalTime: correctly pack/unpack the timestamps. From the documentation: "The time values are in standard Unix signed-long format, indicating the number of seconds since 1 January 1970 00:00:00." The three time values were being unpacked with 'VVV', which is unsigned 32-bit, but they should be unpacked with 'l Date: Sun, 27 Oct 2019 17:10:01 +0000 Subject: [PATCH 051/469] UniversalTime: better check for size on parse. --- lib/zip/extra_field/universal_time.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/zip/extra_field/universal_time.rb b/lib/zip/extra_field/universal_time.rb index ecf23d2a..96304a35 100644 --- a/lib/zip/extra_field/universal_time.rb +++ b/lib/zip/extra_field/universal_time.rb @@ -35,8 +35,10 @@ def mtime=(time) def merge(binstr) return if binstr.empty? + size, content = initial_parse(binstr) - size || return + return if !size || size <= 0 + @flag, mt, at, ct = content.unpack('Cl Date: Sun, 27 Oct 2019 18:56:40 +0000 Subject: [PATCH 052/469] UniversalTime: correctly parse included timestamps. From the documentation: "...times that are present will appear in the order indicated, but any combination of times may be omitted. (Creation time may be present without access time, for example.)" This fixes the parsing so that the times are read into the correct fields, according to the flags. Before they were simply assumed to be in order and all present. --- lib/zip/extra_field/universal_time.rb | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/zip/extra_field/universal_time.rb b/lib/zip/extra_field/universal_time.rb index 96304a35..fc7d3f42 100644 --- a/lib/zip/extra_field/universal_time.rb +++ b/lib/zip/extra_field/universal_time.rb @@ -39,10 +39,15 @@ def merge(binstr) size, content = initial_parse(binstr) return if !size || size <= 0 - @flag, mt, at, ct = content.unpack('Cl Date: Sun, 27 Oct 2019 21:57:58 +0000 Subject: [PATCH 053/469] UniversalTime: tests. --- test/extra_field_ut_test.rb | 97 +++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 test/extra_field_ut_test.rb diff --git a/test/extra_field_ut_test.rb b/test/extra_field_ut_test.rb new file mode 100644 index 00000000..ad2ab7a6 --- /dev/null +++ b/test/extra_field_ut_test.rb @@ -0,0 +1,97 @@ +require 'test_helper' + +class ZipExtraFieldUTTest < MiniTest::Test + + PARSE_TESTS = [ + ["UT\x05\x00\x01PS>A", 0b001, true, true, false], + ["UT\x05\x00\x02PS>A", 0b010, false, true, true], + ["UT\x05\x00\x04PS>A", 0b100, true, false, true], + ["UT\x09\x00\x03PS>APS>A", 0b011, false, true, false], + ["UT\x09\x00\x05PS>APS>A", 0b101, true, false, false], + ["UT\x09\x00\x06PS>APS>A", 0b110, false, false, true], + ["UT\x13\x00\x07PS>APS>APS>A", 0b111, false, false, false] + ] + + def test_parse + PARSE_TESTS.each do |bin, flags, a, c, m| + ut = ::Zip::ExtraField::UniversalTime.new(bin) + assert_equal(flags, ut.flag) + assert(ut.atime.nil? == a) + assert(ut.ctime.nil? == c) + assert(ut.mtime.nil? == m) + end + end + + def test_parse_size_zero + ut = ::Zip::ExtraField::UniversalTime.new("UT\x00") + assert_equal(0b000, ut.flag) + assert_nil(ut.atime) + assert_nil(ut.ctime) + assert_nil(ut.mtime) + end + + def test_parse_size_nil + ut = ::Zip::ExtraField::UniversalTime.new('UT') + assert_equal(0b000, ut.flag) + assert_nil(ut.atime) + assert_nil(ut.ctime) + assert_nil(ut.mtime) + end + + def test_parse_nil + ut = ::Zip::ExtraField::UniversalTime.new + assert_equal(0b000, ut.flag) + assert_nil(ut.atime) + assert_nil(ut.ctime) + assert_nil(ut.mtime) + end + + def test_set_clear_times + time = ::Zip::DOSTime.now + ut = ::Zip::ExtraField::UniversalTime.new + assert_equal(0b000, ut.flag) + + ut.mtime = time + assert_equal(0b001, ut.flag) + assert_equal(time, ut.mtime) + + ut.ctime = time + assert_equal(0b101, ut.flag) + assert_equal(time, ut.ctime) + + ut.atime = time + assert_equal(0b111, ut.flag) + assert_equal(time, ut.atime) + + ut.ctime = nil + assert_equal(0b011, ut.flag) + assert_nil ut.ctime + + ut.mtime = nil + assert_equal(0b010, ut.flag) + assert_nil ut.mtime + + ut.atime = nil + assert_equal(0b000, ut.flag) + assert_nil ut.atime + end + + def test_pack + time = ::Zip::DOSTime.at('PS>A'.unpack1('l<')) + ut = ::Zip::ExtraField::UniversalTime.new + assert_equal("\x00", ut.pack_for_local) + assert_equal("\x00", ut.pack_for_c_dir) + + ut.mtime = time + assert_equal("\x01PS>A", ut.pack_for_local) + assert_equal("\x01PS>A", ut.pack_for_c_dir) + + ut.atime = time + assert_equal("\x03PS>APS>A", ut.pack_for_local) + assert_equal("\x03PS>A", ut.pack_for_c_dir) + + ut.ctime = time + assert_equal("\x07PS>APS>APS>A", ut.pack_for_local) + assert_equal("\x07PS>A", ut.pack_for_c_dir) + end +end From ee028d27463e0f4261d0d149d5f7a038ab81d58b Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 30 Oct 2019 09:27:40 +0000 Subject: [PATCH 054/469] UniversalTime: more ruby-like (readable) code. --- lib/zip/extra_field/universal_time.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/zip/extra_field/universal_time.rb b/lib/zip/extra_field/universal_time.rb index fc7d3f42..5c3b1c16 100644 --- a/lib/zip/extra_field/universal_time.rb +++ b/lib/zip/extra_field/universal_time.rb @@ -13,7 +13,8 @@ def initialize(binstr = nil) @mtime = nil @atime = nil @flag = 0 - binstr && merge(binstr) + + merge(binstr) unless binstr.nil? end attr_reader :atime, :ctime, :mtime, :flag @@ -58,15 +59,15 @@ def ==(other) def pack_for_local s = [@flag].pack('C') - @flag & 1 != 0 && s << [@mtime.to_i].pack('l<') - @flag & 2 != 0 && s << [@atime.to_i].pack('l<') - @flag & 4 != 0 && s << [@ctime.to_i].pack('l<') + s << [@mtime.to_i].pack('l<') unless @flag & MTIME_MASK == 0 + s << [@atime.to_i].pack('l<') unless @flag & ATIME_MASK == 0 + s << [@ctime.to_i].pack('l<') unless @flag & CTIME_MASK == 0 s end def pack_for_c_dir s = [@flag].pack('C') - @flag & 1 == 1 && s << [@mtime.to_i].pack('l<') + s << [@mtime.to_i].pack('l<') unless @flag & MTIME_MASK == 0 s end end From 76cf2290c355d6f4b78f76bb869383e6a9a6b348 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Fri, 27 Dec 2019 16:31:08 +0000 Subject: [PATCH 055/469] Update changelog for #421 --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index 63e01769..54f8830e 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,7 @@ - Fix (at least partially) the `restore_times` and `restore_permissions` options to `Zip::File.new` [#413](https://github.com/rubyzip/rubyzip/pull/413) - Previously, neither option did anything, regardless of what it was set to. We have therefore defaulted them to `false` to preserve the current behavior, for the time being. If you have explicitly set either to `true`, it will now have an effect. + - Fix handling of UniversalTime (`mtime`, `atime`, `ctime`) fields [#421](https://github.com/rubyzip/rubyzip/pull/421) - Previously, `Zip::File` did not pass the options to `Zip::Entry` in some cases. [#423](https://github.com/rubyzip/rubyzip/pull/423) - Note that `restore_times` in this release does nothing on Windows and only restores `mtime`, not `atime` or `ctime`. - Allow `Zip::File.open` to take an options hash like `Zip::File.new` [#418](https://github.com/rubyzip/rubyzip/pull/418) From e50fa4a97b748a5b96133819bde3f3377289dd58 Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Sun, 29 Dec 2019 14:00:43 +0100 Subject: [PATCH 056/469] Remove unused variables from deflater_test --- test/deflater_test.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/deflater_test.rb b/test/deflater_test.rb index e4f552ef..eb58d911 100644 --- a/test/deflater_test.rb +++ b/test/deflater_test.rb @@ -37,8 +37,7 @@ def test_default_compression private def load_file(fileName) - txt = nil - File.open(fileName, 'rb') { |f| txt = f.read } + File.open(fileName, 'rb') { |f| f.read } end def deflate(data, fileName) @@ -52,10 +51,9 @@ def deflate(data, fileName) end def inflate(fileName) - txt = nil File.open(fileName, 'rb') do |file| inflater = ::Zip::Inflater.new(file) - txt = inflater.sysread + inflater.sysread end end From f6639f9b55ab611c8e414a8b5592316e14504b42 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Sat, 25 Jan 2020 18:12:57 +0000 Subject: [PATCH 057/469] Bump version to 2.1.0 --- Changelog.md | 4 +++- lib/zip/version.rb | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Changelog.md b/Changelog.md index 54f8830e..0d12fb3b 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,8 +1,10 @@ # X.X.X (Next) +# 2.1.0 (2020-01-25) + - Fix (at least partially) the `restore_times` and `restore_permissions` options to `Zip::File.new` [#413](https://github.com/rubyzip/rubyzip/pull/413) - Previously, neither option did anything, regardless of what it was set to. We have therefore defaulted them to `false` to preserve the current behavior, for the time being. If you have explicitly set either to `true`, it will now have an effect. - - Fix handling of UniversalTime (`mtime`, `atime`, `ctime`) fields [#421](https://github.com/rubyzip/rubyzip/pull/421) + - Fix handling of UniversalTime (`mtime`, `atime`, `ctime`) fields. [#421](https://github.com/rubyzip/rubyzip/pull/421) - Previously, `Zip::File` did not pass the options to `Zip::Entry` in some cases. [#423](https://github.com/rubyzip/rubyzip/pull/423) - Note that `restore_times` in this release does nothing on Windows and only restores `mtime`, not `atime` or `ctime`. - Allow `Zip::File.open` to take an options hash like `Zip::File.new` [#418](https://github.com/rubyzip/rubyzip/pull/418) diff --git a/lib/zip/version.rb b/lib/zip/version.rb index eb9cfa9b..2af7a65c 100644 --- a/lib/zip/version.rb +++ b/lib/zip/version.rb @@ -1,3 +1,3 @@ module Zip - VERSION = '2.0.0' + VERSION = '2.1.0' end From 0b7b78dc4a7d8e0913ce9a7fffca6e17794c5916 Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Sun, 29 Dec 2019 14:43:14 +0100 Subject: [PATCH 058/469] Encapsulate Zlib errors within DecompressionError --- lib/zip/errors.rb | 1 + lib/zip/inflater.rb | 2 ++ test/data/file1.txt.corrupt.deflatedData | Bin 0 -> 482 bytes test/deflater_test.rb | 6 ++++++ 4 files changed, 9 insertions(+) create mode 100644 test/data/file1.txt.corrupt.deflatedData diff --git a/lib/zip/errors.rb b/lib/zip/errors.rb index 364c6eee..0ff0e1e1 100644 --- a/lib/zip/errors.rb +++ b/lib/zip/errors.rb @@ -7,6 +7,7 @@ class EntryNameError < Error; end class EntrySizeError < Error; end class InternalError < Error; end class GPFBit3Error < Error; end + class DecompressionError < Error; end # Backwards compatibility with v1 (delete in v2) ZipError = Error diff --git a/lib/zip/inflater.rb b/lib/zip/inflater.rb index f1b26d45..586775ae 100644 --- a/lib/zip/inflater.rb +++ b/lib/zip/inflater.rb @@ -47,6 +47,8 @@ def internal_produce_input(buf = '') retried += 1 retry end + rescue Zlib::Error => e + raise(::Zip::DecompressionError, 'zlib error while inflating') end def internal_input_finished? diff --git a/test/data/file1.txt.corrupt.deflatedData b/test/data/file1.txt.corrupt.deflatedData new file mode 100644 index 0000000000000000000000000000000000000000..95fe8720220844b84e77a2b9ecabebe886707cee GIT binary patch literal 482 zcmV<80UiE@0MTxnFcf{>{fa9!51TglfJv3cnzTZrO$4cwhiS+$rdV}s6dQHj*U#Vt z3=5f`xX0%l?mad@^t@d^Mn6{hdb5q!PZ{3gi);W^yKNff%Q)Lw#4v5bKfDIG+wJa? z=pnns-~~V`F15*%_4Ca zj^EboH)XZqO6tya0#)-~Treih@%_}yQ1_j5gZaJAdUeEVT>IuDsQSN`rbJ4Y7;mF@ zf!i26zX>!yBbTKhhP8Aj^y*W$=fmwAo%K2sJ)!HNmwM3k8J!jDh3C2&Q7T4?A^j^} z9r3Ik8+;@B3zEjD19@fmrW#RnN-n8r3f5ArlwiSXCJQF% zdpJoZSw_p{^uI6; YT71LBFMz*PQb9>fNlr&oR8>Ys3fq$HbN~PV literal 0 HcmV?d00001 diff --git a/test/deflater_test.rb b/test/deflater_test.rb index eb58d911..b34f3570 100644 --- a/test/deflater_test.rb +++ b/test/deflater_test.rb @@ -34,6 +34,12 @@ def test_default_compression assert(default < no) end + def test_data_error + assert_raises(::Zip::DecompressionError) do + inflate('test/data/file1.txt.corrupt.deflatedData') + end + end + private def load_file(fileName) From c897bbdf77754e1fb308d619dee4279bdd71a842 Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Sat, 21 Dec 2019 10:38:26 +0100 Subject: [PATCH 059/469] Add Entry#encrypted? --- lib/zip/entry.rb | 4 ++++ lib/zip/input_stream.rb | 2 +- test/entry_test.rb | 9 +++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index f6d5cb5e..c8300c92 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -72,6 +72,10 @@ def initialize(*args) @extra = ::Zip::ExtraField.new(@extra.to_s) unless @extra.is_a?(::Zip::ExtraField) end + def encrypted? + gp_flags & 1 == 1 + end + def time if @extra['UniversalTime'] @extra['UniversalTime'].mtime diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index b9c35111..87f0c010 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -124,7 +124,7 @@ def get_io(io_or_file, offset = 0) def open_entry @current_entry = ::Zip::Entry.read_local_entry(@archive_io) - if @current_entry && @current_entry.gp_flags & 1 == 1 && @decrypter.is_a?(NullEncrypter) + if @current_entry && @current_entry.encrypted? && @decrypter.is_a?(NullEncrypter) raise Error, 'password required to decode zip file' end if @current_entry && @current_entry.gp_flags & 8 == 8 && @current_entry.crc == 0 \ diff --git a/test/entry_test.rb b/test/entry_test.rb index b49783d3..613b2605 100644 --- a/test/entry_test.rb +++ b/test/entry_test.rb @@ -151,4 +151,13 @@ def test_store_file_without_compression assert_match(/mimetypeapplication\/epub\+zip/, first_100_bytes) end + + def test_encrypted? + entry = Zip::Entry.new + entry.gp_flags = 1 + assert_equal(true, entry.encrypted?) + + entry.gp_flags = 0 + assert_equal(false, entry.encrypted?) + end end From e072c57bebbe2dcad6697eb24a1b8f7075b705b1 Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Sat, 21 Dec 2019 10:41:24 +0100 Subject: [PATCH 060/469] Add Entry#incomplete? --- lib/zip/entry.rb | 4 ++++ lib/zip/input_stream.rb | 4 ++-- test/entry_test.rb | 9 +++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index c8300c92..f1963d8d 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -76,6 +76,10 @@ def encrypted? gp_flags & 1 == 1 end + def incomplete? + gp_flags & 8 == 8 + end + def time if @extra['UniversalTime'] @extra['UniversalTime'].mtime diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index 87f0c010..39611980 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -127,7 +127,7 @@ def open_entry if @current_entry && @current_entry.encrypted? && @decrypter.is_a?(NullEncrypter) raise Error, 'password required to decode zip file' end - if @current_entry && @current_entry.gp_flags & 8 == 8 && @current_entry.crc == 0 \ + if @current_entry && @current_entry.incomplete? && @current_entry.crc == 0 \ && @current_entry.compressed_size == 0 \ && @current_entry.size == 0 && !@complete_entry raise GPFBit3Error, @@ -143,7 +143,7 @@ def get_decompressor if @current_entry.nil? ::Zip::NullDecompressor elsif @current_entry.compression_method == ::Zip::Entry::STORED - if @current_entry.gp_flags & 8 == 8 && @current_entry.crc == 0 && @current_entry.size == 0 && @complete_entry + if @current_entry.incomplete? && @current_entry.crc == 0 && @current_entry.size == 0 && @complete_entry ::Zip::PassThruDecompressor.new(@archive_io, @complete_entry.size) else ::Zip::PassThruDecompressor.new(@archive_io, @current_entry.size) diff --git a/test/entry_test.rb b/test/entry_test.rb index 613b2605..8daf7adc 100644 --- a/test/entry_test.rb +++ b/test/entry_test.rb @@ -160,4 +160,13 @@ def test_encrypted? entry.gp_flags = 0 assert_equal(false, entry.encrypted?) end + + def test_incomplete? + entry = Zip::Entry.new + entry.gp_flags = 8 + assert_equal(true, entry.incomplete?) + + entry.gp_flags = 0 + assert_equal(false, entry.incomplete?) + end end From 4a4c553b1e3c1a07fd068c3e94f0201c8e771329 Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Mon, 23 Dec 2019 16:30:39 +0100 Subject: [PATCH 061/469] Move :eof from InputStream to AbastractInputStream --- lib/zip/input_stream.rb | 6 ------ lib/zip/ioextras/abstract_input_stream.rb | 6 ++++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index 39611980..f02dd813 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -82,12 +82,6 @@ def sysread(number_of_bytes = nil, buf = nil) @decompressor.sysread(number_of_bytes, buf) end - def eof - @output_buffer.empty? && @decompressor.eof - end - - alias :eof? eof - class << self # Same as #initialize but if a block is passed the opened # stream is passed to the block and closed when the block diff --git a/lib/zip/ioextras/abstract_input_stream.rb b/lib/zip/ioextras/abstract_input_stream.rb index 7b7fd61d..58678a3f 100644 --- a/lib/zip/ioextras/abstract_input_stream.rb +++ b/lib/zip/ioextras/abstract_input_stream.rb @@ -106,6 +106,12 @@ def each_line(a_sep_string = $/) end alias_method :each, :each_line + + def eof + @output_buffer.empty? && input_finished? + end + + alias_method :eof?, :eof end end end From 1b6aeb2cd0f3b39a338f497450f0d93ef7199396 Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Mon, 23 Dec 2019 16:54:56 +0100 Subject: [PATCH 062/469] Replace Decompressor#input_finished? with #eof --- lib/zip/inflater.rb | 9 +++------ lib/zip/input_stream.rb | 2 +- lib/zip/null_decompressor.rb | 4 ---- lib/zip/pass_thru_decompressor.rb | 7 +++---- test/test_helper.rb | 2 +- 5 files changed, 8 insertions(+), 16 deletions(-) diff --git a/lib/zip/inflater.rb b/lib/zip/inflater.rb index 586775ae..92f8501d 100644 --- a/lib/zip/inflater.rb +++ b/lib/zip/inflater.rb @@ -14,7 +14,7 @@ def sysread(number_of_bytes = nil, buf = '') break if internal_input_finished? @output_buffer << internal_produce_input(buf) end - return value_when_finished if @output_buffer.bytesize == 0 && input_finished? + return value_when_finished if @output_buffer.bytesize == 0 && eof? end_index = number_of_bytes.nil? ? @output_buffer.bytesize : number_of_bytes @output_buffer.slice!(0...end_index) end @@ -27,14 +27,11 @@ def produce_input end end - # to be used with produce_input, not read (as read may still have more data cached) - # is data cached anywhere other than @outputBuffer? the comment above may be wrong - def input_finished? + def eof @output_buffer.empty? && internal_input_finished? end - alias :eof input_finished? - alias :eof? input_finished? + alias_method :eof?, :eof private diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index f02dd813..b41eb00a 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -157,7 +157,7 @@ def produce_input end def input_finished? - @decompressor.input_finished? + @decompressor.eof end end end diff --git a/lib/zip/null_decompressor.rb b/lib/zip/null_decompressor.rb index 1560ef14..1583f568 100644 --- a/lib/zip/null_decompressor.rb +++ b/lib/zip/null_decompressor.rb @@ -10,10 +10,6 @@ def produce_input nil end - def input_finished? - true - end - def eof true end diff --git a/lib/zip/pass_thru_decompressor.rb b/lib/zip/pass_thru_decompressor.rb index 485462c5..a7777f1e 100644 --- a/lib/zip/pass_thru_decompressor.rb +++ b/lib/zip/pass_thru_decompressor.rb @@ -8,7 +8,7 @@ def initialize(input_stream, chars_to_read) end def sysread(number_of_bytes = nil, buf = '') - if input_finished? + if eof? has_returned_empty_string_val = @has_returned_empty_string @has_returned_empty_string = true return '' unless has_returned_empty_string_val @@ -26,12 +26,11 @@ def produce_input sysread(::Zip::Decompressor::CHUNK_SIZE) end - def input_finished? + def eof @read_so_far >= @chars_to_read end - alias eof input_finished? - alias eof? input_finished? + alias_method :eof?, :eof end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 6d11af6c..5034d26d 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -84,7 +84,7 @@ def test_mixing_reads_and_produce_input assert_equal(@refText[0...100], @decompressor.sysread(100)) - assert(!@decompressor.input_finished?) + assert(!@decompressor.eof?) buf = @decompressor.produce_input assert_equal(@refText[100...(100 + buf.length)], buf) end From d20a6834a3205c5cf859e2810d1406ecc76b2ef8 Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Mon, 23 Dec 2019 19:51:11 +0100 Subject: [PATCH 063/469] Rework Inflater#produce_input to use sysread This aligns Inflater#produce_input with PassThruDecompresser#produce_input. --- lib/zip/inflater.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/zip/inflater.rb b/lib/zip/inflater.rb index 92f8501d..f929e9e2 100644 --- a/lib/zip/inflater.rb +++ b/lib/zip/inflater.rb @@ -20,11 +20,7 @@ def sysread(number_of_bytes = nil, buf = '') end def produce_input - if @output_buffer.empty? - internal_produce_input - else - @output_buffer.slice!(0...(@output_buffer.length)) - end + sysread(::Zip::Decompressor::CHUNK_SIZE) end def eof From 8f7c5caf294e2cd26de176a14e7b1dd7742342e7 Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Mon, 23 Dec 2019 19:56:34 +0100 Subject: [PATCH 064/469] Drop #produce_input from Decompressor class --- lib/zip/inflater.rb | 4 ---- lib/zip/input_stream.rb | 4 +++- lib/zip/null_decompressor.rb | 4 ---- lib/zip/pass_thru_decompressor.rb | 4 ---- test/test_helper.rb | 12 ------------ 5 files changed, 3 insertions(+), 25 deletions(-) diff --git a/lib/zip/inflater.rb b/lib/zip/inflater.rb index f929e9e2..9259998f 100644 --- a/lib/zip/inflater.rb +++ b/lib/zip/inflater.rb @@ -19,10 +19,6 @@ def sysread(number_of_bytes = nil, buf = '') @output_buffer.slice!(0...end_index) end - def produce_input - sysread(::Zip::Decompressor::CHUNK_SIZE) - end - def eof @output_buffer.empty? && internal_input_finished? end diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index b41eb00a..de75989a 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -39,6 +39,8 @@ module Zip # class. class InputStream + CHUNK_SIZE = 32_768 + include ::Zip::IOExtras::AbstractInputStream # Opens the indicated zip file. An exception is thrown @@ -153,7 +155,7 @@ def get_decompressor end def produce_input - @decompressor.produce_input + @decompressor.sysread(CHUNK_SIZE) end def input_finished? diff --git a/lib/zip/null_decompressor.rb b/lib/zip/null_decompressor.rb index 1583f568..3a90bac9 100644 --- a/lib/zip/null_decompressor.rb +++ b/lib/zip/null_decompressor.rb @@ -6,10 +6,6 @@ def sysread(_numberOfBytes = nil, _buf = nil) nil end - def produce_input - nil - end - def eof true end diff --git a/lib/zip/pass_thru_decompressor.rb b/lib/zip/pass_thru_decompressor.rb index a7777f1e..e5e76864 100644 --- a/lib/zip/pass_thru_decompressor.rb +++ b/lib/zip/pass_thru_decompressor.rb @@ -22,10 +22,6 @@ def sysread(number_of_bytes = nil, buf = '') @input_stream.read(number_of_bytes, buf) end - def produce_input - sysread(::Zip::Decompressor::CHUNK_SIZE) - end - def eof @read_so_far >= @chars_to_read end diff --git a/test/test_helper.rb b/test/test_helper.rb index 5034d26d..960f71cf 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -76,18 +76,6 @@ def test_read_in_chunks end assert_equal(0, @refText.size) end - - def test_mixing_reads_and_produce_input - # Just some preconditions to make sure we have enough data for this test - assert(@refText.length > 1000) - assert(@refLines.length > 40) - - assert_equal(@refText[0...100], @decompressor.sysread(100)) - - assert(!@decompressor.eof?) - buf = @decompressor.produce_input - assert_equal(@refText[100...(100 + buf.length)], buf) - end end module AssertEntry From 4e28f7286cd9ad6d657727f0ee7cffab68024b47 Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Sat, 21 Dec 2019 11:02:01 +0100 Subject: [PATCH 065/469] Untangle encryption and decompression --- lib/zip.rb | 1 + lib/zip/crypto/decrypted_io.rb | 39 ++++++++++++++++++++++++++++++++++ lib/zip/file.rb | 2 +- lib/zip/inflater.rb | 5 ++--- lib/zip/input_stream.rb | 3 ++- 5 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 lib/zip/crypto/decrypted_io.rb diff --git a/lib/zip.rb b/lib/zip.rb index fa382376..8778556b 100644 --- a/lib/zip.rb +++ b/lib/zip.rb @@ -21,6 +21,7 @@ require 'zip/null_input_stream' require 'zip/pass_thru_compressor' require 'zip/pass_thru_decompressor' +require 'zip/crypto/decrypted_io' require 'zip/crypto/encryption' require 'zip/crypto/null_encryption' require 'zip/crypto/traditional_encryption' diff --git a/lib/zip/crypto/decrypted_io.rb b/lib/zip/crypto/decrypted_io.rb new file mode 100644 index 00000000..ec9cab8b --- /dev/null +++ b/lib/zip/crypto/decrypted_io.rb @@ -0,0 +1,39 @@ +module Zip + class DecryptedIo #:nodoc:all + CHUNK_SIZE = 32_768 + + def initialize(io, decrypter) + @io = io + @decrypter = decrypter + end + + def read(length = nil, outbuf = '') + return ((length.nil? || length.zero?) ? "" : nil) if eof + + while length.nil? || (buffer.bytesize < length) + break if input_finished? + buffer << produce_input + end + + outbuf.replace(buffer.slice!(0...(length || output_buffer.bytesize))) + end + + private + + def eof + buffer.empty? && input_finished? + end + + def buffer + @buffer ||= ''.dup + end + + def input_finished? + @io.eof + end + + def produce_input + @decrypter.decrypt(@io.read(CHUNK_SIZE)) + end + end +end diff --git a/lib/zip/file.rb b/lib/zip/file.rb index e0c62443..45017822 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -49,7 +49,7 @@ class File < CentralDirectory MAX_SEGMENT_SIZE = 3_221_225_472 MIN_SEGMENT_SIZE = 65_536 DATA_BUFFER_SIZE = 8192 - IO_METHODS = [:tell, :seek, :read, :close] + IO_METHODS = [:tell, :seek, :read, :eof, :close] DEFAULT_OPTIONS = { restore_ownership: false, diff --git a/lib/zip/inflater.rb b/lib/zip/inflater.rb index 9259998f..6ca5a7bf 100644 --- a/lib/zip/inflater.rb +++ b/lib/zip/inflater.rb @@ -1,11 +1,10 @@ module Zip class Inflater < Decompressor #:nodoc:all - def initialize(input_stream, decrypter = NullDecrypter.new) + def initialize(input_stream) super(input_stream) @zlib_inflater = ::Zlib::Inflate.new(-Zlib::MAX_WBITS) @output_buffer = ''.dup @has_returned_empty_string = false - @decrypter = decrypter end def sysread(number_of_bytes = nil, buf = '') @@ -30,7 +29,7 @@ def eof def internal_produce_input(buf = '') retried = 0 begin - @zlib_inflater.inflate(@decrypter.decrypt(@input_stream.read(Decompressor::CHUNK_SIZE, buf))) + @zlib_inflater.inflate(@input_stream.read(Decompressor::CHUNK_SIZE, buf)) rescue Zlib::BufError raise if retried >= 5 # how many times should we retry? retried += 1 diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index de75989a..a9017039 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -147,7 +147,8 @@ def get_decompressor elsif @current_entry.compression_method == ::Zip::Entry::DEFLATED header = @archive_io.read(@decrypter.header_bytesize) @decrypter.reset!(header) - ::Zip::Inflater.new(@archive_io, @decrypter) + decrypted_io = ::Zip::DecryptedIo.new(@archive_io, @decrypter) + ::Zip::Inflater.new(decrypted_io) else raise ::Zip::CompressionMethodError, "Unsupported compression method #{@current_entry.compression_method}" From b80ce3cc573495caeeadb48af33bafef48d06f56 Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Sat, 21 Dec 2019 15:04:43 +0100 Subject: [PATCH 066/469] Make decryption generic for all compression methods Now, STORED files can be decrypted, just like DEFLATED files. --- lib/zip/input_stream.rb | 23 +++++++----- test/data/zipWithStoredCompression.zip | Bin 0 -> 42875 bytes .../zipWithStoredCompressionAndEncryption.zip | Bin 0 -> 42931 bytes test/stored_support_test.rb | 34 ++++++++++++++++++ 4 files changed, 48 insertions(+), 9 deletions(-) create mode 100644 test/data/zipWithStoredCompression.zip create mode 100644 test/data/zipWithStoredCompressionAndEncryption.zip create mode 100644 test/stored_support_test.rb diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index a9017039..e4179164 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -130,25 +130,30 @@ def open_entry 'General purpose flag Bit 3 is set so not possible to get proper info from local header.' \ 'Please use ::Zip::File instead of ::Zip::InputStream' end + @decrypted_io = get_decrypted_io @decompressor = get_decompressor flush @current_entry end + def get_decrypted_io + header = @archive_io.read(@decrypter.header_bytesize) + @decrypter.reset!(header) + + ::Zip::DecryptedIo.new(@archive_io, @decrypter) + end + def get_decompressor - if @current_entry.nil? - ::Zip::NullDecompressor - elsif @current_entry.compression_method == ::Zip::Entry::STORED + return ::Zip::NullDecompressor if @current_entry.nil? + + if @current_entry.compression_method == ::Zip::Entry::STORED if @current_entry.incomplete? && @current_entry.crc == 0 && @current_entry.size == 0 && @complete_entry - ::Zip::PassThruDecompressor.new(@archive_io, @complete_entry.size) + ::Zip::PassThruDecompressor.new(@decrypted_io, @complete_entry.size) else - ::Zip::PassThruDecompressor.new(@archive_io, @current_entry.size) + ::Zip::PassThruDecompressor.new(@decrypted_io, @current_entry.size) end elsif @current_entry.compression_method == ::Zip::Entry::DEFLATED - header = @archive_io.read(@decrypter.header_bytesize) - @decrypter.reset!(header) - decrypted_io = ::Zip::DecryptedIo.new(@archive_io, @decrypter) - ::Zip::Inflater.new(decrypted_io) + ::Zip::Inflater.new(@decrypted_io) else raise ::Zip::CompressionMethodError, "Unsupported compression method #{@current_entry.compression_method}" diff --git a/test/data/zipWithStoredCompression.zip b/test/data/zipWithStoredCompression.zip new file mode 100644 index 0000000000000000000000000000000000000000..045ab9d484160e96184802da1462ee6886761560 GIT binary patch literal 42875 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 literal 0 HcmV?d00001 diff --git a/test/data/zipWithStoredCompressionAndEncryption.zip b/test/data/zipWithStoredCompressionAndEncryption.zip new file mode 100644 index 0000000000000000000000000000000000000000..2fd545e977663a1caddcfd851accb3c6baeb5252 GIT binary patch literal 42931 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�yUU>KDSJI^ra1z!58lJ$m{i|gkqg!z=*&!R4fQbreR7z9TDP%U!y ziS7RzybK2=tUPM%nr<#B%Ni(ku;)K`UK||)_wTJ2cw?wMi zKIxIkGTBT~8MkQMu@U&a)MEk-KA3SyuGuyo6z$>M(x8C6*YRQNwjd^L*h)qZW*~B@ z9;b>WM>~4|aCi#NSB0S05LUxx!n9+>iGxR0%)RF78oSYcJz?k!+++RJ1$IpLNwkmi z8PTVy9!h)t{ZjgXxjwOJvG%z3LDAGT7_!1n+Oig)&~>>#MH0tdEh>s?*Vgz??n97-cD!GVtus?Z z#zwu%5E&HqFRrpCT*Rz>zJNp{T#@uy0j;Py!?UD}%npa$ zdJ}m2=$W3pGUE604rdf@pV<1?g=hvh7qDY$m2IG$P~;dnP}jP1)qy~*jLV9)Wi+!) z8O&Wso|+zsh5)>%!Rl!7Haf=}+ffKT_(YEz@cbVjqgHb%KS&Z?`MC(14+|ldgtiiO zc}x>d*@4q)D2=?fS1*|5%LJ&wa)`QKrW#L;E-`#@iNa@3Qx91(oK-_8U*6=x(=rR! zm}%hB-b}+bQ6k}{Leq=EI3%T4s=&NjhqS>7CJV8Hxq~d@{)V9heR}5m7!BDOLnhG5 z{KH;~c?9SF$GAn!PioFL=j)}AL6@1#m1WJ6$}0gGi?qGDrd(NZ2EA$g1XPic#^8Yf z+M(MDnjiOrip63H!}LPr&7R-UTX@K)E-Z8xg{ZmzVpv=ZMxf9gTWK|gC;`rzfUixz zFTTZ-f)vT}&4uBRjoa|>HYWv~`(S;!pW)G5YGwVo1$ld4p}HpbJMHgxzy$pJNsfQg zO$5#0jAqhtVq{*OXW)o>1Ll16V+La9B?}ZZ{u%W|+BEeqh6I$xe%ZrWx4dYvSl7DyWKjd03< zgMak7($N#3(b4wP)|*@?p#(}Mx-Od7C{W0h3eNeb9hD&c6~KaR%V>Hp>2Ir5 z6tfx%Gnd7ulax%9Xw1#+v;C?N@J6L=pPeXk;J-3;gUe2kTP{FD<@$Pn|7vM8J$tm= zK#ZYI^jJOcbY8!X3EUrLOZSBXxw zwxQ$dZ=4*Od=2Zv;Wuae{A3RNfgFPlBOMmv6(NpP`QRF9%DI6QAIRklGwy3CfM*O3 zCG0s?qr-R*lM5P=MGy{1AFc9Ehfju*VeE536wS;Z-x-t0-5l!@k`0~1|BfK3B$au(D%jL z8MbVeWl!j+mzUuneKi?!1}0w%UqzP&Zbyx98{ko;aRywRahDrZDo+pcr#indzWqQt zRxFN<5;-iQAqM<7n0UB0J*X=r$}M47iA6w3@Ja2-0R9#Neb16Q1g1WCN@ppuH)zH< zSDwN6S0%1sczVBt?!x{GjQ?=Bdl1`mWj=P}i3<;H@Q7<;J9drMC|I>$pmnj|n(`S1Ypn&J0qdJ%&~Ts6Or zpUD8mM+dO_7Kz+GU0~zIk}|?8$8L;G0#X`y{zNe3DvsOmoa%JD0jP=2w8^navxdnb zXRpMI9S1H~+=eJr1QXr-a%cN)2zV*D-kh~tbiBEHn;$Xjd3GLBxl!32uYTRi}v4vN1Y|KjrDHQV&pg~HH? z%$TIc#)E0Yx2w|;+hOr_7CS!+#qB^d^lC!rwLp&=ZH2axn?I^{uU76P2cr+c|1w{c z$BR}Q!;tTn_oE`S5)AnX)Nyck=Hy*QXzDpAzr##(CuP;S+a|pilJJ)we$wPdIm?0# zJP01TYmmrU;;W9Ae}nzVsvT)UkSh^3&11NvM^%WS0{ky@OuTgbs{ih3LvSN{j@}aA z=<8Q63g+aktQ&-G_H~QUdx?pMw;>my5AsMfE{!8JEK5Chj>%W$_AZU^GR|7mpfi*Gpnm#K(S+?Dkl8v0VVHEVPeiQcYx-n zKv=ga2{(1VL!!C1lOqh-3#e}bwuQx0=f?a`S~wEfFEs${0$MUp9L^-My;b()`6g?L=4C38c4Si#l(e)K#(W6%a<-Szc<;*I08BM)$bgx$ z+Mu@K&R(s?o*ChW@FP_|CZq;gqkZ_ElD64uM-Lf3-{a9R3gv;#!sbRy9rI|^{tJq*%;1(-hgDMANpPZtYJ zd&V^+Sr()b+9#}yh1OU7jM%k@v&)4eTDD+jIf*QO^)5;TeA!QP_x$xgZ5XC4gr;tQ zT=o(8>FBoBMK%=H`T z0t%Co@qT#*MD&Ms*GyG2MVche{Q@%q_haS}Ma?)_223Ug+3-nqviMPSNMw@0A_Q>k zi+c&z#2e!QA51zUPy-YoNwB+O5G?pT^y!TOGGgu;YES_>;D*uUD$^$kXaG~ z9XYG!Ds01G<07~{3Q2UJ|5n<738LHujDDV+7;obg_I7|6iIp4-ogCRIK2b=uJ`xet zAh7>m$Cy|@tmO|l^c3y&Yn_TDeeAQ({E#@qgk<%zQMYdZ>lB|O*G z-`&ZwR?rhz3hKJQEyd3jqX)ANj6&0mE#Ku}W@n!U+$u-#P8=OEv2$`ktBB4LK&{ee z>V!+Tr;#1~pA;9MWNUG8OFWZ1Q&jreos_Tacugk74)sk-;4l_i^lZa>c;L|Ge4KSFl zzfhH#Z>c_7aUIzy;;W;J*3a$L8ZMQN7w~0eE4(g0K5*pmu8I4u6OV$Q^WT+9x)&zq24SpqwGdb;Ne z=3(*Zi=lcE08O0aiftncAOq=sv zjJ?j>*sMc`lRzv8Gvaii1V=U_n%9Rmufcla3R8Ym(cjNiF}JhC8kspQq(txJR8FUy z`SjS@hdD^y%uP3RNlY;%&x5xWtIEq>vFMgxg^2>=Eo$8J*4g)hOeSRsubyY_GNPp3 z6XZ5TgAGwcZ*m3St})9nn$t``bz7$49>dFc4uFn9>He*yJX<52$b;&@ddi$X*KC|K zy%p=TSlPLU=E#v7E>QyjXuWR%3htrWJu0n}jc2;t6{4pNcDArC4q|aLhxwwyQ)PS? z8bQL2W|wiCMR9j1D}2MfuGG+`+a-e9$=gBF(~pNBt(Jv2@O|6$c>~46&wqE&e=;XA zO;r0og)=P8{C!9S=2nN4!tl^`A1L@>-U!RV2e;CwHxKT73JExwSCF%4&vlxFUKz|z zFa5OU4XT?%$vE)&d^dl_8C$ubpDqvAH{4Vy@Zmll%xt096VuLE!Dcnq1wOo`oSqE2 zXy1SZMiQI;5S%)=S2$NTZ4hwQ#6t+X$zeG?bo%(5|LDzFefBvoL^#ptnBIJ$C;-2Od7_9-OCbg z`;!!%1g6yGNnNe#9dhTTC#&jhRp-iWg@h`y$0!Jnn2u@wH75PXl7PHp27k;T=|raL2}c{Z11Z9$t-eov1gQbh=igvMh_%MQ z%!I4`o^(%N%=kwH?((=q3$gy`E5D9JLp(WcLj1VVef@dL4!nB4DEzdOzb~T+*h^Ft zELm)XSM&w+9NXF(yDtus=jHXq>OC{fo;@gy%uGS*xpr z-siV(sf?;yw4vUYaVDqzGe_;ulCQcp%aLZ=`KrJ?#C!sk<2kBHO{8Nu&8q2E`F&Dh zX#?u4uz9)%4>TUav`*j%68p2$xhqa9a-vwL9C=pTUl6rX^`u>1r}BMoxo-OILvAl^c|O_YC;)}egBZ!<7gj#mzYIh$`yC+&4BszDFk z+r*l)7F%{w5oTeJ5}_EijQ=B(iYJ$0!^whJCy9QnB$|fz@5PPkCAF(wo;A4(MlLCW zr^AL;7H?if2HmJ_$AF1V!T&3~dM(+tfmr;U8aX*)Hf(^iOAux<_J>SZqR=_}Gw7Bt zKrLbkTcL89j_zP4!*=k!_mOUhS+ zy{oN~Vv1S)F8?6H+PCnotZ?6<10UTYCWV@RJ8D9v-N`(6_QXU)bR8DPR7Mb0*Q2~D zZ)ZJG8}Lg7BmF84F2S@rk-T4l?IE!Q zyROPrCP#kCY{`3eOI8cJTY{Rg*@pRP{;a%gEo24wn8C890%{{j;sQAz1IKu3cRhZ4 zuq<4kcs*HXhK@GYq&4T}2|%+m$mke;3+5jW>363lC~GC|+eZmemu{jPzm{q%QkuI` z5?MW>B9K@p6UA2tyr+^zTgn##8D!4uV`!7ik{y13LmOkL&7zDM1r2K`k4CxApgWQa z;K(w^L>=|LLH{8Xd;nco)}LrBf=hh&LF6b755tgy=E1HUym+%#x{dKg0Qk{^O8IhY zNk;iB%|#bG0rnGz%<>rh(quCOi|)5-PI+QNZw}nZSc~h7+?|wSh7CiR^9AZEFVZYj zJ9Up26)eW4_RKGg1yVf$VydC|bMIAcY1}cMvYV>&TO+MiUfBqyt?_D&zDHiOFiDXf zWvSLkFrWSU7=2q0Z;qe5)$CAak?&C&S3-wqP11})1m;`OACm?;GHWujc+?Of)>|N6 zDDYN%k_&zAD_S-$lb%?zD4q zD*y*vB0oLK2VT8*+JKmFgnV#a^d4G(Rmq+Oo>ho*RId5$Q3;G!iEk{YnZ&n}`LNkn z#nJx$1ZdU54h6ZI-xeUMnuD>O@PELTybJS;F~Hcm`~0l3^KemgSV^&0ZYGKQiiHsA zEc(d>Y}MvRrkuK8DUxDckIdm7*Y-awK@9AKe)aAMhn$J_hj6rhll(0F1Pq%W3!@Tp zgJ)M|F*YP(1enR&@xIzrW7B1YE-0s&e5I?d%o-qcXErY_tT1+3%Z@ye5=K{?iW%Lu zJ@N53a+4rq#e0IgGSTPe#P+k>eRewbw|7(4z`dxr7qKjnYmmXHp0-ytnKLXpz1HB2 zqiZS$!NL~82y};-*$^`xe4YxwI9XS;wX~=%dLaGLgW_vW zGomg>H!a2yLgkS%)N1n(FTA)aD}%P`%J)x)ilV0Mw%PNu(PvBsoihK%ALx<4bv?Ao zu1Up_&6rUo*3C+WmS+*jVBHX^;%yv6O^zdwu5b=;oZII1+hBk2?w@Y{+HlW)i334? z3j6@&j^-2?KROLqh<@v=kJ?;IeWZ9c|MDtL!myhaStobKdgCh7Q4zih&Oa#{5k@3X z{e%DfYsiA1hg5L~hJqSCB9F$CjW)aeU=!xav-Zm$`WdE?9#^w$wg~9 z5-vEcg`om!60wy8s7{IN^Yeoj3+7j#Es1Qw3r3ytFY1_?#TU+o2^Ylxc*PN4dCbuN zwIS#P#Qt~UnkPHv!g*I&KpauH&|)yPsY`@N5|DsshiJKjNs2Ny{9mVkHWB@_p8c>8 zD~o#d;Pqe79CgBK|80buRU#D$)9bP%cfGKVV0gaJK4#ED$TTL%tj69-j_1emRDbze1`cdEh^C3r8mGO&~B2vt88J;B=lB5 z&G}A!r_XrX0>oT+X6f{H^qL|N0C0;1$R6y6tx?gjJ4@y--H?O6;Fcyp99yO4N?Q2+ zYcetUj%cllHf-^$*WU*sytGm^Y=rh?@P1AKcUoexme$7`iO1mJuao0oKkk+8P7-umR1-z*Rs+c12 z2ukodM=Spv9Pf6vnvtdH^Onpk*Qtf=@%lM2NQ1VI677Tk)cBs5Dh|3XmKW6^<;zL72UtUAw)ufxW_EoQ50PjQ4|=;}{=DCh(AL!a~hsQw64 zl({f8>DQqxf@OqT?DmiNvQ7Ytrm#0jR}O}gc)7Qpl5<40UaC;f3Ii^pJa9zeWxDI1 z2!It`R5+=j$F8tm^<>SSgVzuE#6f#6B~dRkc!e&vRxXpN?ZhJ4c|Go3wMsCc8HuWr zZP(zrtEidpZKec$Ed+o9lTPNbSI1q`m`>xAYy;&dRPJ5D|1c#^ozF2>2hda)v%@sT?u53foB+ z0i~9pDa8X~zXo_dZfCS5Xq@;_kj*{L7bc!8ZV_6@yuQ?^S!f^T*J(D&G2JxTY~GN} z?lR~~^1PQY)kYO(Q4~g(&x|Rrn3a`6Tq9K4yZ0R0GBF2Hk5gPrt`H7oyQy$dlR5c zzr>%_KPvtW9VX0E0Gfe}@F5h&&LmhptZF@6Tq}Kw1D>UiZkXM>RF}YWt4-4rGE)m0w#pvjWLf^}xLU0VzP%zhq190|P#EPt?I-PJ2D6 zFE(WrOer-0_DU{}$N%V(iQs%r>}ep;15T?_W|t*0sJXe{?L~L>gWiV9 z4A+o@hjhG~t25ScI?WSIwhvV#24Q!;4=K6UtCwxt830Z)1K6ym2W%i(V38D*9+tio z6~5>cq@O(8sY2SBQ1t&HU{*R;NsaMB*e$n5G)9imF#De%jQTL1A=s=nMI0+-x$mBa z9=m_=;5Ln7*<=d4go1fLTuej}%*>!s{4Sx(2>wyDjr|2u#Y`)S83SfNtudb5NT-;7 zHoV=vIaR&<2yoDJQL$2;O;HX!C6r=_ik-bd|7drK9gRac-&$s#2UKwn(HswyKk|PZ z*BcAqL{0f){Hi%lG-~1Jbn4Y%hCBSaF|YxwdvdoKt{gMs)!VoK$>&FKK!<(g*TA55 zPKR{SFWVF*$nga<@Ed(kt9XCmt44T3ot~oi{2;e!%f8OL4z_9+*j0ld_eI ztfhl=`7j;TNHrREd>bsooc-AAm$>}~Z8*2urSlb(XYX<0%pd1-kA$Pvq_mFS@SqAj zpo^RVlGh%EwzW~Vqnd@C&wudcE`UFLz(KU(1^ekzemPs$t3&Pa;slhGlbhD0koC{l zO^cod4rE5W;wD!e99b_wXwjhBs;qkD5+I`-=)oW464PQTj@4`c&SJp(0{G-^xt>$ag$A?HBr`kj1L zu(Aq;Ib_zrkl4H!@d|oKiIE#}Me;eBi&G3rPdmi{yf22me}II9GKn&!ZtT$3fD%GV z&Uk7&k_d8QQB5)ZAam>bA>UW(XlIo-#fyCJQQ{-|*tB2~^ndNdQuOru6VTe6I9+2? z#fOn%^?<2Ji69?8@?4pk{bZ6e?Z6CM>Ym-&ZFCq?s;Sk^0v>?~U~B0t#BuIIVMd<8 z@oZOCk>k{1vlp%ExTCUMSax!GA-_sKeB4lUaGMq;w-_5@Oy7r2rB|>^6RG%$tJP~i zRB(Hhoj8jqNWFvG#*c=(WCZ&E>J%PB?Uc1Vm6TXfYTTOvhEBXpKz8<4hcJJZvY!3P zLq!*>2{1=sJV4qh4hc$0%Ei`kw_7Wzp~;+MdNZFoOH~(NJAf7*OmT=aLea7eVaIjZ zZ9^bQ{At)(#VQ!7zjyArt#09x*lO0R@mO-$yF9|%Sq6gS@ZGc+=zs~TR5~S@Wmv{@ z8F-T>1@7;yH|tU0f!$)I?LSIM(nd(ihYo^y6SoC~-qg{KxC_$C z(Sh6(Z1ylyY9op8jZa@>XecCvMSG5FSad)VnV&_dwGX z{3H}@wWmOba9QYFX{N3L?9iy^RoW>WMU^i4n2?LMpt;^|P%ZlUs0}kQ6rb1O(cgq$G0Ar!MsAphz{R-4QtpUCavi(F1x84 zMJy@cdS)HADeJ4YrROmdp^#9Q+lx%@uNUAOzr(GEcnrvT<_B0p!bC5{L)Au`Bw{Bd zuRK|u#0yqrMe*$FyTv{iO+NN^y^boh0^U>gYh=C0b#vP{GVus&!2rPX{BqU$7ungc zo4Ux0(w+&et^wCb0*$Ijw!|tWzUhAs?@Kkx+ZCy&k_c zgolJcutof% z>#*WUw^5GMIHU&GE{W*uF7S5;yTMm3ZMNwgx6_|rQAt-RR`!p2v3Hw%h^N3zhl|6f zvV$?5CdaNuvnhL`ENZ#+W!_-(!~(|oe;O$BfOxSqQ!iN`$=LniDGH7{)y=Jx!U|WG zeo(OzWy~RXxmN5-pJ2m2>oM~Z7JN#92R~~A`mb9OkrtBAAUuwI7PNvaEK7PbS1osI z@aM;HbJDASLrcj-cJ%iKTQa7<2TM#=VKc?uNO(TFld7Q7h4>?y$IRpZdJ#vw zAw1N0E+=a{U)vQnZ;sXmin9KWR0+Ll3{s}&i%x`a&>_wZB4Wo+S+9c9Mg@|=7@V2Y z^uA2*OMw9fk3+!i!9`+^zEB-WM|ONsVrZxvzoVqJ0oECNCR*m~iPBe}?Nai&rmlu#bqmGrzq8UsgcK$u0UXbE|a=X2& zFszyTz4#a&eUlB?d$ZJJ4PPTaV2hj({wLQ%_7%p5Nb598JVefX&%)~4-~);wzF`Hw zCOUZ6#=XhyJ$tUWtGXCyGd(jH4X!{c{YpP-BI}m`D_x)_;kO?0{@~Xaq8+P9H%DwX(OQ^>aLe63Oz7}e@H%9 zZ%#WH z0p~3hC|I8c5gRM95|if88{S+q7!>0CRjc=SdNIs0K7_m*=${n@^DXWn^tGTZ=ka9- z_MIZZBUEAILPL;m>f^%m1wB*aP2f_D+umZ%TAioUPB&FbI&@Kr95~dPP1CiB^wI{_ z4;E(-O8fT!y!xJknBbzZ=2vt6x7vkT1DxPnlWqH}Ua^{1AVje>_pNK^K)Ij8| zJD$ZVS1M^rV04kz42|HOBL^Nn{0wRg;~Y#`)=a4XYHv47xSM);shQupF82Q8bWv5{ zpn~*qn=AH<*mE{S5LAVXwO7psAYYa6RQ#{1Op-iey)%_sd8`H)ZNDl7iV+Zt4`|_B zNa%iT1AWazD0cJXkS#*0WiVHYKiJ*e3y_jpM=9{qJY(HVz0!mx%n$6vTD>IlFXYZLJei`K+rc$h68MR z&R3%?Ne3|}@5&pxW(_Mj$p$ye&hD^7$)zMdBJ-3;<}h(0948J>1#y9)rdF1kVwwXS z$Bh1d$TM}~JHrwPSF4~-skSB>AzhU-+S<&yg6tIa(dw5wMugO+Y;A3k2lvaHX4^DI9r@xWgSNVWFUnrt zC8}6K^@jjeExPwVtFqGSSByWco6^)Cx~Q$%yH60vkbW7Qe|;~0P9D|#m13`LT+ZB3 zZU&Eg#s*9_{=>A6n{N?U~9in=}TY_o7fy` zpuTyZolycFig~Cw+6kLHG^tU3$nnu&?*RLUWc{*VzO-!b{)6j)|4;bmSaF5=Z^IBv z7u>u%HpMIB#9}#aM-BEX>-$a0Pg|%P1F$0)*~R}iTAT~4nIKeC_U}N&(k7uRo!zwU zH-8O&$>{4o^k5Nh@xUZo@H)BT7$syhhuQo^2(Z^$PP4eH+KJIAB&&l=>iLYxp$;Yz zPw3AJ!FU`Qqeyh(u2*iL>BICiyS-?PCrN`peR;W#o}c5}=vvVIM;|v`E1OsaR|BBR z`ny3#q9U{~%#cElb?@oLBiFkwi&A-6umYS^0kfk7UqC2S|Nc{8w*8H!-X{*PPYmN@_Ov@J=MaD|IoOTaBx8;KI(58 zni>eeN6K}RviU11nenLMn2w)^A0~zivb(yXsEpJmns1O|te+Wq8HTf&9fsxUR7<7) zQywsK{8%9542|f``!y+uaZaq1o%k(G$L-@J)?m-U)$V>oX@KHF0py+p2xLDoRHP#b z(<>Uq*02b6BZf;QP~2u$clZ`*=?`NQs=X+pJU|0^Xe^=6%*(sgUdM7qEkR*0`Fw?! zX|9G{xA~I8#PdjIQf?4}0}LfsRAIFLVz6Y=?L@A$3OW|R{Ryb_;4jYl2v(;+7{Hj? zeKYN(_@i0M>n;rNHN66BS7Xl^d9n(z1GTlC2cP~KpQ+FOeslRxHq*p3sMfx;WNF;s zB(2wTnkgi}gQ&g2-W1E%*-PWG@d-~o07e$Y(4lYwj`h7?f%-iehjssufvCop& zE&=7I!MO-7nM|^c0Pwxe$4oPgBcQ(*K?(4}B@8F@UfT|a1repX3gBT_UhW;$yp-ki zy=zaL5YR@a{coK%0-n))P1WaOwIfZm_3g-y-h4K2Seb~=qw|Gc^6qBfZ2 zEc|i2eM2~?9&gi2ga|g1quLm$bqqF)b4GFKJ>SpP#j*HD2MR}Y2;~xh%n^}g0qj(6 zX}V+Ka7re%ZrsL$E?WZL{+cdHlgLvs)zGgG;xk2Z(&b$vU5MGs>{twYGmlT}3n8+1~H#rmT58B4Hn(KOA5vsc+FR z!+#;g!r;rbW9A7ow95uwFdR_9#e{4IO~yx1orA-#ci-vm_Bu};b&u314IqX2mrxwn zZOX?VtRmK-G)V|Z8+=fiawY1)C5#&rSPNU<4b7?3U6Xb8hXlRJ`k)*sTEPGUQTv#o z4WuMt{o_VOw>#mhuot2mpKa(?Jl<2bftX`{ZWDGn=? zri(}{hpIZg_DZ-=U4Xm{k8iq7HZJmpU>6C^r z?iK91DG}e&m2iZ*v#07o1kR;Hn_NE3meh79&N0TM)B;NYmsy;eQe<#vcF9c>gu|n8 zu{B7aN@_dy+GDcj!r!cheF)@RT^Qo!yv2Hok)jiw7yQ{6)%W4;2NV2J zG2O>QD|j1_=T<&Ps8{nQH2CoY0I?9Jy*jqL*2LMZ2Xl!9`B2}$Ld@|*AIHB>5JK*L zB6(cBnY=HjWc8=)UrTYGacA{MWL{NUfb}F=Pkr=&Op3Lw@%H2`%OKrxAa3w0M)lz$H7y(1X#cAa-BG*HcBDq%o`h_5DB%^yArQo2i}x zJD@?h|AiOi!Li8lcvp?%Os{QGfqD&8e;9NX<2jL6PtpU%op^tv8Xz#cr~APhbH$H1 z$di6Ft&7evl-f{ium_M)ZJ#EfH736=$$!UeIOU^!|M79%Cv5v6B!2zZfgi^~7)4^| zfoOM?euR>>tMT7OWfnw(XXJ+uP4HXkJ@y`Q6M<%zRav3<=id=W#Ovz!p1XR| zeth(h6SMw}TdNP;(f;wom$tRb0VxAS)-Jm)e@+4BF4UE&vYN&zQcvl=)3+!y6k~bw zfGDQ30EGVLXru`t%Kr8t{S^8pQT88&>dexof)4D%^qD6>kt3Gd0^&iqzuwTKPn<1& zpl0*uSUw{o3-Sw&s6O*(2C4e4BlLDLJ-)os`r^rg?#g?_KPJ@;P1v^H7&sPnsyChW z@`KKwyB~ijiJi=g$zw7um-;507mJ(m9N=Gw7&D-QI@cqqD2cLYi#0`G+X(&=*Al{= zV{A6WIRyrFIjW9Nt_wK!<;1;)$pDT$Np_Q%kk5ey%?6Aj1%ETSuVjx^UAqWLJ{MYxxl;IRleOGFmv_@p3}H2crCb*#1;6ERPKDwRYT3@|BP4_ z;^Uf)Wj82S8vdiEOJ^^-a<#5N?s~(^ecU{3;PC)Clq}9v$YUKFqs~>Jm##uTxV#Dcn{~0K;Jz!Fp@p83>AeiuIPJ*6E1IK?TBz8#5Ho5NP(=Bi!`TzRs%0xxm-dcv<}IV4ZLf)q!0TwI80ob`g~$k!xu zFBW`aO^#6Ny#edlIUrt5rnWCgm`Z;nUGTk2ACz|4tsZdvSvCvvcRmdp$%4>6cu9b1=R8OR0%UwJnJQNkh>KlaYOz73769R!2u8Y{3{^rnC_^ z^bDslH~kp`W)`o*_-Z0xcD zSTTE3jSA8Dc+5JS{4H6}CAgHnv%N|w#S1ibPp{mA$h8`CIBzo;PfMV5uK^(rniU() zTsK$1QzUrrDY^qAY;mnWk7~rbWb(j@BY{9-`!}$d;v0ZbB;%Wpy!9tFH{%|47yuy~ zN-Em-^^FzpLOO%%j4!~l=8284SsMv(KfQK*7i`^6gt#fj)zP#gbB|0G9bq2uFIW2A zN0D_Zk%fyWsJ|N`SOeNNS>5O|c1^huBm=iF@i=3kQ%;a=jxt^uxmkc?Z-NViebXvT z7@)AAEY9j917k@toBchXWM$AI(^(PLqzO2FfW(S<=rMh<1b)}T-^Y?JtaVf^uX9o4 zf)8rQ$EPZ>)Q%ob+@5=3CO>~**~k}J=uUfaOV;&U1gd-JCZ4M3*p6VZjhH1Lkt-u5 zgdA&2;{f>5BCfDDt@T1G3y3@gSLScr8~Y~!UQ8hAuX-xc^)z&34*d)w5}BgU8puX> z=aqNn1P>|qUNAqwuFZf-O6NT!g`~^JYcLfB^zNfpr$wWk4ns8YDT>M5YQtHg{npB# zSeD~RawLENZ{UD~COade;enI)Mi8m=G7vrcHkBU<6G&5K8lx19(Ih+JW;g*Y1CCCP$V>WO)Rr=gO-`w+?HNq%f_+>WGnH{h*+4v)W}02OFyl3M8TacXlg-+2 zuE2UuewSN=U`Wk-88QlTmV3fJ(OwnTo6WG zTyeqwO+Uc;T%U}=iM~q?qBzyhyzv?d6mkyE+MK_RI^jqnPJ|};TSTc6g~8x2{^z#Y z4f);AFZ-aqX7*g?`MWrm>z7Dgh;N0JFf^ni0&{!h62B}xXrprzi!G1jLk=2`%^Y=; zR|cLk6xf<$CpCar-u{hFxq3>^)!e?k{EX-+4shV0CPKeuccz+od#{7#J(pI1`HY!e zrxn_g%B#6H6Avb3p!g6r_ZBfWboy!B_fE`hCte!`!hc@dzZ~wk;e(^J@phP8|CLyh z6?GfBwF?0Ge;_mARzreuG)r|&CZ5jzKA7MDATg`l-%oY7li~uF(7-&LhcN38y~Q2Z zf0jH*BZ-}u4Nu4vfWoLZF%EU<+6uvgqL&iOo|(SnSU~nLrZtyKaed!5TGmk6OCUkm$J0KGM{oY-nRC!*MvgS(xgsxDr8SlkQ03V=0R%-^ycv339X3>}MXfXY%#N`;3%_+{edxj0 zaM%JXu#2OkaGVwmQP&emxm|=Sa^N3f0U6(OLWASj5o%MR{6PL#uMe3AGt~m}|XO*m!G)vu^ zv73vXAJO7Tn^~cY?$hYHfUl!ysk7smH6Zu`XR)8Tgp%N=YNq6=m0qY@-K@4V6?bdR zLvYu6@y%3PJJlqM9$L4OG#K!%5e^yo3Y}5>Cg}%0_cC#XmjK6q=Bzen38}&@b&N{j zM%*ei0}ja}3L3wND6AX=*VvFPE1z8hp{pXHyWxMS_s*?+KjHN38!=jh10;pj{v7B_ zdr}%`p(YrD>GtvjHVw=T6beNCh<S@`PeEp}4QEBxthuqCd@^e_t3<(o^pE^wM zQD+jX62p2YMOU9sDBE9Ok2)b2VG0r7`I$8bOgnrgC@{h{BkS}lR?XtCspLo@dP%q7 zUWQ2usqM-s=RJ6msV^xd5L*-!>*npAqglM9iAfhPi?;Z*Q12iWcQtRte5gWk^hL72Sa-`bEP%hibiYa#Z4+GeP#MV_yaAtG*O@!i-zGFfAdUXMiT>%+&qVV>y1MMgRK}^hf=fCbjL|uJaXC+0mK?+-@)+#27;&@@$hKK~=jk!}NHzJ5_~Z z8>0(s=j|Y_K{Zr7$W0JgF@B#@*kuRrz_oV21Lzp-86(GIQjOWHrjrxCr&(Xago%rN=FWJ*+JkB`3DfoZPWDDpP8lHqaDBuk%QbYfBoM@YOV62WBT%zRX z{Tmx3QIyQy2$47EINq$^!hOdI{L>tOr%*MY$|Jorp&X%?IQ=lkb(V2B7cT*!L$RPZQ z$X8>`bOX7>-=EK`={5gF2x30RW_D;JZJ5YV;gHwYp)iS=b%PqretIra$&+^GIs$RE z#8VN*}vCd;TaIoa%6H^Gtcl;gNZSj@%Fuham zn3llb2}z@FhQ-elZlp%4pvzfg=_vBZv*(JoN-73~PRpZt0xR4R%xHOzc4d&Bk_xtv zCJgJ_yz#2D?1%AE3JITpEtnp9!X!Lwah~azU$G6Sf z6tB&u$luHpJ$d{yJ0I2KJ@Qo@(a%~}t?20_dkI=&ZQ1;crWRKqe#BNgNoEd~mtT=L z_C4c$^qIc{L3bx|6!4%LglD47J=`R4XA3M~GBn+ox{>9701xDVFX8_L6kpBlvI`|u zMZ?4Blb=KZT+-Y^BMAIM`7Qz+?sX63mAyNu6en)01R3%sT&H1;^mV`ocxk1`{4$St zS2Ts->)z&M(wP}89%z(aDM2A7@5_pqqtk@6N*9&O^ogA8Ud6bsM-b8njL+~!x@$Q7lhze-9bR- z^aG_X82EAN1x^V9DW65_B2R24G0&<1=V3iTG@aDt2H}hhjBw1$Uo0#r*-uE6P(Hsq zellXoo0gq*|+$<*5NWru-Lw z1?F_F=ut2rPI2iBajN-y7phM<5ORu~EM`<3|8t{vYWl7{ld2)n)NN?oG`t`4Y$ zQw3!jYf4cEcU5a=Q>?kcxuY=y?unR&(~rLjD8aY2ookIW8K(RHRy=GCVveGZ^ZBGQ z3SD!Joya*p(ZD)Ojvw+#rV}Rf8JKy!ZIh>Uz4-E3|GquPU4k>$^fpu?rP@|QGaR|i zAS>g;3x?Nu>UH&?rD1$SPNrYjjdHx0{=JPe?Oh9D62`{yva#V{gJgT40sP(XPn=uw z^H8(QLjvtzyBkdS1C#cQyXm6W;dMoT%U0oTAkZ2%Bb+J&Z0;4Tx z9p2-=#Z@up9L6S6CY5JW#gFApFM$lR8(c1byLtfuwY2s2Ikj#IGaR<6La9n28N&d9 z5nax+nnxN??AXeS%|1rPt(GI7MJ6z}-4HyT7l03XOa(=dZ@;>LA?M`ZnEfe{L>;S| zM--_r6H_Kf5guBfsXu3%dZ*=f1iDiyCmxy(?|I@PA!7G$rp~P@@4Mo0et`XCBwiQ&0q)@#tF#3Sl!~l zZEZBZwOZ`5=t~(`VJTm)O$KX)i;J1yb)Ss&h4h!)x&VC43gF~md(Gu` zIieCH{S}tUMse2e1QYttfS$Z_|A2zn&em4FfB^RioiwXi+2vuPR`X_nm#5xnqvFGS zHEiwTz6mtnhg8vggvbGF3-;#{dp#F6tFov1T59!hHHUm*oO;9j)zX=rj6*OfNAZqO zl9^SLyWB$b0PkpVq;100RUk}S5%K-;Uv1eCIPz<<_+ML0rZms9|0PFzJ!RV=KCTvyH^%AC#Utdz)Yv9}*KK%D5J2uAB9>?sE>_>hpuI2+fE1Ph z!SpV+TGZmjP#E!QB2{tHwW;Ia6J}B>hoWT+CN8{NJaPlRJLxy3@Q$2IJ=rklbCSvia8|hR z`ctzOlzHjI zJpn54r^Hxb!~264?k3PCqNHh4!Z(xweN3XdZgcfvdt{Pb9mUHc!h4Gn3)x=eX2pRC z!4t_vR($Fgsq9rMk{FSK(cgzG&EE_5-G24o%I$TxOZIM?64lCe?@_jK^^{j7BFxg# zp>tCDmxh1Oa8lcMdp-Xg7NmOwkih5C*iB#I&4o+Hk`b)m^D_%nZ5?tMM65A7e3_N? zhKK2(9x3(Mro@0NpvlIG>WF~1X=Q^`efNLi!e#M+1}PO|Y~3~Gd<`!(i#m#=sRWPMi0RzI~i2Lvcd5EBCJJmeAs(*m|t312F{HIz^tG15AejKjv0 zye*fHY?1WdZA+9U5(hNV7e`=jPZBm%(4~mIiF~LFsz|JMXfxSXx-l%^tSH-Gk~1(z zVYiQV`_A+-^ZjBl6xR|B7OV09)7taQvC(8@on4z7EuWD;Bh?LqJd7+?F~7owW07fK zB<8E{G7YDS7B{lR%y}$9pu~Yf!oqn?xrpQBqFyEfLrEi0MyZRa2WAw=lE!&;Obn{x zY)GLV%5OAtC04Rp^M7ZoSX?~m{T#E<@p1!NDf_4CnZhyf@R+VZs=;II+D3fm(D!ejQ{a&#Ifn#hzYN_Lx4x;MQ!R}p1tDF!J)cPe=4Wq8dZFyX$Cv!vft zc1H|S!kATIvoHUvE}fBI0^`@Q)D)z=sZ`WvM!)@E27&x@X079#hf?uR7ax(_W+1TQ zK=;n`LS@-XMo3%Dp1Khrkaz#f7-~AGvHt_Rzx9k}+ul{P#&Ai;tUVJzBSRWggvT7_ z%N0JXYNwyxh3o0z9zvd~+E1wE+a|`A0++|uDpY6Y0A6e^TQOf}3lSE6kh;7T*nbBH z;RY3LzQH8;1C9S+rxK3f024tu;3kN6labUQlENG!Yk|JNgcj0PSykPy6LA1v;qpx}p5ip( zMCw@9DE@3BV9wNJuT31yKJhk-jnw7Z_V`oXC=Z*eHPjC=xx)pm%MQrg-cy_(#c;4!628W*qq;uif#}9 z0!^QNwgxL7LOJ{Wsc=t)SZI=r#HJ~fvhlSUYy4n+XnXpudKF)Y85TA)7w$?y(uvn= ztzPul!!c|&pajbs(^AuIYc;;lWrM0M<>_`X%mu3>FKBPzZ#(7cmM;4w-21&~RM})| zq-ZQI8Ht*Kgez>tLjWfa!c%JUW$x3WJZ`gt0ZDs`+y4wTVuoJEr>;+n4qgS%iIOc0 z0dgf>k}Z1Xd{M(lqLG7-g2%6{_NseGD~xs$K_)Es=*@FqPqyFMWr)Lf~61^LT0?PqN$ zrTUs>^5{f{O%j4T^kj%|J*f@887$A`0jz2J6cxlafos$}I-nND!ofYJu=*&Iq9&^C z{IE7J{1g+0OjYx6sl%&gI%{RI!y0&&iaT{!#XL~tYTH4I!oMvcCixL1&lpfVu zxIX$wxmhPm5|OXH0kuY@tv$mbF&ku4ZAIaf!Z)_9=0vXNqROOtE$S2CTi^9cm(xp9 zgAB;fn=?Mg&8I>PagX4kaSE7kJ9FS{>E&@$Aa38c80lR$&6y@veM~unhl9wY&&$d$ z&!5+GS!!eVMlBFt+?O^SyCL|~&kF&wtc!o*|7_(&Y|r1r;Yw z)cKCy3Raf6pXJDS{~*% zd%qod+R3Gt1i$2MzU*v;vAHUs>C(-Et{y@A5s|Atj+hycku88R4i5qyG_V)6%~OHy zAcVvTb}MbIJY@HNGCB7PZ_*B0A(lrFyk2NyLOi||C5Jq>l~Fa%$G|ku!n>4(3@)?Y z1hqoa0s&SliNQi#c8-Fre;+46BOXeVkKHS`MS1%uVLX0*+L8kGC$h$Vx^@-C9%r`>BSxr&`;4rs- z@%I#>3WU6)a|u4R5o70FmSY+9b`B>tf5XEwR8OMZ7fv?B!DIHgK7>M&;op!vpyrR6 znk-JJkbAUz!#PYFftrB&QD_w`D8XZD(z|e~U6x>(=@S|%#kpc00G}Ondz^Zf(>zMr zksw31#Cs!`a6WxHTVaruoN`b;7KCZ==jyL0xDZ{a_)Ew0UY+m3as6&imwmE1sJ-hK zMk5%NIW3q)TqKh11w9V9dv5ep9@f}oS_ZP%qZRFJpiAVR`e|uH&tQ3Qd>+TT2s;9| zd1zV`wP8W-a;|4RJ=<2!sCt`tbrPQv!hijV+dS zCQkzIEUe%|7LILndJ`(WCw;Ta9YlQKV?@PcG+nAhy1?Ck}=;?ePYgeArhr&x@~=qEj$rrJ+hDrTIX;DjA5V&96O;)d#; zo=qA0I!;-C=+e?b$gBf&qZuE;JVMoj?k9F+mNaRJiVqukdq-}7c2+S+*8n}n^~FMe zvx?HTrt~*hZ5P66nG!O%jm!EDy}-OOWH zLJZ`NrKfB$H2#_a^i#L4O2E7GtE7yJ@NWYrdC#zXQ7>}P1Obcaz*U}y~+ z!8m#zN0eu3VLE>80<2Lh8B3vuPs+htIe*;ih^?_!IuGrvX?q$t&{b%?UlDdV<+Di%v zVZD+)NcuIojB1pE+$kB&m!;+JAW-rW8^L~nHS)!Ktwgg#GQCHz0y5+{%>_Yi0eIVAg1PK$gCC4Br|pzh7ej! z{DO08kO=WjE09F@oM^pp>iY>#waL_=5^z#$S4ZJ&01DMA!MGceH*aejmyUY3swU1m=}*l<2n~gp3?%t zlIgC>F50VLbLfablr-MPe!1KREmF%T+R?z2_!C4Rj=7CwG{lnS=>s%U+IFA31uY}h ze|QH}L7HXcD~ZREzFh($Dao798)AcFe!FdbRZJzovhBuQH_qTNxC{*L?rhv` zaCdiicXyY;8Qk678Qk3+?wtFQm-CX>o$5;W$67!2(MhG&`mG_DT~Igo9-?B#6YzQ< z=Vwa)oCGlV(pZtnioVZ0dQk}e%Dm(R?$X@EUw79)_}~oSYuI+=-D||dvg<-{Au#lx zg=lwP81FmL?&jnEl2(7FlF~tE{=^8iogr0i2;D?4{>TNp2U_x3B2cuSV2q22%PjQys=uKPGFD-e%THT_erS}cQx ztmZ=07+$G6=s_tW$;p_AkN#|gJ=5Dajf%Fg5Y&4AmN&EBA+ju3r9%?Q=AbYD)u9BN zfUI*05^#!!%rE5WI_zXtW!>8M<{WdSADIRda&6kXd(3EIJ8IvAOD4ph1bkz9sD-sC z-u}J(#{+N}LbY%`YVU43R0f4lt7}_@+08KJ8<&DNi-yS`y$Wy&z?g<;^#u7`H7Lhx z^)yjKw*78r;qut`jrOlueB&FFef6%!-Syh61-gnxTteKrdGjN07)o2>H~^9=w=zEo z^N%aRuu%FAAQIKO7Ibnmr9K5@SkE}g*~}n6e&7ntcfv^*#m`LHtF^xrtwG!m0i;Ye zFB%hJ7D4H?c64);dUaCAD=4?dKEnEa=!y<)6%z1P^>Q84@CmkPOL>c)rOuv`%QfN# zu3dXffi)Y}6;pTu!Z%WoRjrg|Co-97UUm4%o3BW|m{ z!yUxCkFF2I7B(OvF1zCUyxQ|mw`3gg{e?j}1FR83{$*&2hwGJ(5O*U}2bV<3x8DcpVDX{}X{R2D zYAFok>j3`-ciZs}rPrf)Y~8Po9=8n5a!#-!rmJDoxJHaK$i!dec{jmqFLTN3s#Z-Q zGR5{-Qj%8dO*4>uI^$7C6uWImufMt~VO5P-*t%oHsrt~5uq2?M77iPuVO){li+$`o zdi;-@JyY5QQTpDwrNAcSl?z%&upLP40}R`dOtNnc{we(AA@GSl3i7Bw$Ot0JS*o|B z#cg&#!VN{{+a4-lHpS2xY{f`f)-SN&i4! z6551BP@9Nx?ps}#@a&k*rb5y%dab)YKz~LLRn{jdf;QSSzEk7PdFci0?T_-;H)t3N zguOhu&tU$Z+P+ zlz4K-wAeRWrloRBFjD^A#5OlvQfz~Jr?Ut@09lzp=mT+Cia?=hxCZIaiwYY0Pcm*L zc28T!C|XRvBFlwT!!2s&9Of@V?U#VgW7y%ZRP=$ePB!*A5V2mg zSXVL~1yoL!IfsE-R4-)NApe6@#0LQv5=?T1%6=`NTQiu68TJJB7DVY?GV?^$ElRh4 z-0yk1d{S)Uz{|1}>1y-PU3h2K_#lmBUKNnXRgz&a0i%dyT4>OLlG z_4zq~ZNZTJ;TukSW4(z6JpZ-DRa1NK3Wkg?G{+tIW^g@$J62ZkuS>tRS%1~`?*$5^ z3ZAm?b*3Ikf~jBFfj$?Y-e4I9&+#|3YKE|< zw(5E64j&CZtOa%*%adm#tuh;;n{eooVL$15XXp004<8Or39vX@hwuUhfP6otcTkR1 zXoLnK`0REkTq=SE1Z)eX`BO;D==Ofo1{FWc*9JCUf#>GD%TtE4N;bc1B_Q+>Jr2jk zGiSq{+DnSS@2}BN@Xucd%`pZd>QL&dE|wQ0)}(P|xB(UV7s)cf+QnW>rk!T1FYF1P zA1YnJ9vc+{59$ZGNuRit^+HZF-RfL@C#dfUp7_8zCkj8N$0P3ah|;J!YvVRlFL#e; zU~Krh80OXuv;j|ohk-Yj;R*pW4J1&;P&UTN`r24Mi@XS&>pUni7Iyh=Bz!id`6q=P zO!_*JESs%^gFc|b->?HNItNF~I4+Fpm^Z>S^+pjj!8Uyv6D6O5Anp<{;_q?rO z@AWD3Vr=Eeo9)Lxynd-r=k+!A7zE4d z#}>wNdltux#i0Hrqi7H}(-N!LuuewT3zW){?g=@HR#^`XPs{zO+xNM`F5;d-9#>Ni z(b1+3Oqeh8S8j*u{Zp$IHSaNpuOdt~*2?!3b-)mhiG~?&%%OE!VWzkjV4wO;Q?iFp zGTSZvbpkB&6|0WOu9a^QW^9=Dmk^&tyIw$BpUu4ASjKg9O^H-^^fdDLO)r<*gJ7K# ztH0GkoHD`*M+-GMbX9jlb%fwwlW0A{OXT^A9&>Cy-9^M{|7oLX*m?%@S%QKRSsg*E z00y-ozf}ZfL@f=U)1Wb?Z-P}${`=@~*kH{n?Px1}g_)Zw$0#scV@a@1C2h-30kG1nk>U zs{UC{BGF`(1$_D~>#?pVCC z6)g=et@AF`@#r+hJo@QQRBcT6LSs|3--s|`h->jIx!eriUqU?@-C6^ZIg>biZPYkr z&6uK*ejh|Tl$rJ5B+DtC+l8n zbaAgSB@1+VFkmko0(+@GeF6TGi#&RMGWI{JJA@miNT@}ooD)|lBo^m=_4`r8B7^{h z$5UEj22QfiFx&tBWQKpI`6eFsM02l}``;wAzS;~=P8=*Od-~6z(KUn)`)^xK8Q5QK zx&Ang7J9ku?<}f;CK{6!lvw-uNfY6d!IgzH z;)ilX&La7Zt*u_x%4vQ-+`;(4<1q+XQ_UaN_!zY6%4Cbg52FO8&1`nrIW&lsBwCuo zOZoP;3025mKZs^1XB^l(Oj=&U%b5kp%mP{V7?sLrsgr_^k5}U3`(q6M;;E-> zO~JZZzU?Y_TCGaQOT@A#gjuOxeEW*{W>_0gk7`<6ljqLN9h$S>W1+G=L&F}Gjyb8l zjK7gt8WbL#o$qw-C0QMye|-%7{$YrD6pJ4RLkq*Xu#p{>)A#sMRX*TO(=EKI;rSH^ z8QIbd`pbsh4YE0W*|lyFRq3fLB^AG0!_wxEBWo+u5VNDbUGSK#p|()v%ek$&8Z2ti zbFNbz!(>ar;XK2{r7M4QnTKfwow^Zfh`8=(I7h%N=(iwXqEBZ9;JuZ!lLUcb?!XN$r9blt9e^pFNjB_nT7k^7I}{=8jJ&~7v@KZHC7F?lSfzO}TxvCkj% z3HxgmE6i`6nmRwp6Uo~?4YisCp-T&3r?(N_7OrP1D{EUHkDfy5!2gCzXb{H}CX;Yy?R2JKmW1F6&CXl)w>y-V z%{f|e92jNI0Vhtb!JMkI4U(Nuc0y=#^297oj`U$Uk53Vc$_7}f6lD9rkwX|-j6=*$PGn->JcxuzH;8TuGc)V0S#TICjY zUFd=SDxEGs@*vz_LW|2>M-y(J1}aM07iynWe9o!N9!4iO3rKMPO51Dus$a{Y_3^`q zym1_Jm;?Gak4P1weM}U+5miN1%Tjv-y|VVz76e1RVe-zg_j|X-*%!Q!Ozb!d<;(NU zWc0<#aQ!u0An$oYqt+-O$ZJOX`H2PFh*@-cc0-rG`1kikDF7i!y3?RV7Qrg>30+Q$ zv1Db`%#xbEzuCF7FkB}d3+=0mql6O!&!8)q(QFkf9?#?({l5Xld$<_#Lb;^_OveQ5|`gBzZZ#vW}&)w%gGgOLm%0;-4I& z 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 literal 0 HcmV?d00001 diff --git a/test/stored_support_test.rb b/test/stored_support_test.rb new file mode 100644 index 00000000..8260e4a9 --- /dev/null +++ b/test/stored_support_test.rb @@ -0,0 +1,34 @@ +require 'test_helper' + +class StoredSupportTest < MiniTest::Test + STORED_ZIP_TEST_FILE = 'test/data/zipWithStoredCompression.zip' + ENCRYPTED_STORED_ZIP_TEST_FILE = 'test/data/zipWithStoredCompressionAndEncryption.zip' + INPUT_FILE1 = 'test/data/file1.txt' + INPUT_FILE2 = 'test/data/file2.txt' + + def test_read + Zip::InputStream.open(STORED_ZIP_TEST_FILE) do |zis| + entry = zis.get_next_entry + assert_equal 'file1.txt', entry.name + assert_equal 1327, entry.size + assert_equal open(INPUT_FILE1, 'r').read, zis.read + entry = zis.get_next_entry + assert_equal 'file2.txt', entry.name + assert_equal 41234, entry.size + assert_equal open(INPUT_FILE2, 'r').read, zis.read + end + end + + def test_encrypted_read + Zip::InputStream.open(ENCRYPTED_STORED_ZIP_TEST_FILE, 0, Zip::TraditionalDecrypter.new('password')) do |zis| + entry = zis.get_next_entry + assert_equal 'file1.txt', entry.name + assert_equal 1327, entry.size + assert_equal open(INPUT_FILE1, 'r').read, zis.read + entry = zis.get_next_entry + assert_equal 'file2.txt', entry.name + assert_equal 41234, entry.size + assert_equal open(INPUT_FILE2, 'r').read, zis.read + end + end +end From dd74bc0e7311730f81e844d01d9eb44ab8f17a3e Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Sun, 5 Jan 2020 15:51:08 +0100 Subject: [PATCH 067/469] Cleanup NullDecompressor --- lib/zip/null_decompressor.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zip/null_decompressor.rb b/lib/zip/null_decompressor.rb index 3a90bac9..31ae9e11 100644 --- a/lib/zip/null_decompressor.rb +++ b/lib/zip/null_decompressor.rb @@ -2,7 +2,7 @@ module Zip module NullDecompressor #:nodoc:all module_function - def sysread(_numberOfBytes = nil, _buf = nil) + def sysread(_length = nil, _outbuf = nil) nil end From 4ac83737b146560bc02c2880a6565f691458832a Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Mon, 23 Dec 2019 20:57:47 +0100 Subject: [PATCH 068/469] Cleanup Inflater --- lib/zip/inflater.rb | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/lib/zip/inflater.rb b/lib/zip/inflater.rb index 6ca5a7bf..af25fa5c 100644 --- a/lib/zip/inflater.rb +++ b/lib/zip/inflater.rb @@ -1,32 +1,33 @@ module Zip class Inflater < Decompressor #:nodoc:all - def initialize(input_stream) - super(input_stream) - @zlib_inflater = ::Zlib::Inflate.new(-Zlib::MAX_WBITS) - @output_buffer = ''.dup + def initialize(*args) + super + + @buffer = ''.dup + @zlib_inflater = ::Zlib::Inflate.new(-Zlib::MAX_WBITS) @has_returned_empty_string = false end - def sysread(number_of_bytes = nil, buf = '') - readEverything = number_of_bytes.nil? - while readEverything || @output_buffer.bytesize < number_of_bytes - break if internal_input_finished? - @output_buffer << internal_produce_input(buf) + def sysread(length = nil, buf = '') + while length.nil? || (@buffer.bytesize < length) + break if input_finished? + @buffer << produce_input(buf) end - return value_when_finished if @output_buffer.bytesize == 0 && eof? - end_index = number_of_bytes.nil? ? @output_buffer.bytesize : number_of_bytes - @output_buffer.slice!(0...end_index) + + return value_when_finished if eof? + + @buffer.slice!(0...(length || @buffer.bytesize)) end def eof - @output_buffer.empty? && internal_input_finished? + @buffer.empty? && input_finished? end alias_method :eof?, :eof private - def internal_produce_input(buf = '') + def produce_input(buf = '') retried = 0 begin @zlib_inflater.inflate(@input_stream.read(Decompressor::CHUNK_SIZE, buf)) @@ -39,7 +40,7 @@ def internal_produce_input(buf = '') raise(::Zip::DecompressionError, 'zlib error while inflating') end - def internal_input_finished? + def input_finished? @zlib_inflater.finished? end From 2bbcec0e348ceab4842a060f2e51ead4f2d4e5b8 Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Sun, 29 Dec 2019 13:06:50 +0100 Subject: [PATCH 069/469] Cleanup PassTruDecompressor --- lib/zip/pass_thru_decompressor.rb | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/zip/pass_thru_decompressor.rb b/lib/zip/pass_thru_decompressor.rb index e5e76864..32217e07 100644 --- a/lib/zip/pass_thru_decompressor.rb +++ b/lib/zip/pass_thru_decompressor.rb @@ -1,13 +1,15 @@ module Zip class PassThruDecompressor < Decompressor #:nodoc:all - def initialize(input_stream, chars_to_read) + attr_reader :decompressed_size + + def initialize(input_stream, decompressed_size) super(input_stream) - @chars_to_read = chars_to_read + @decompressed_size = decompressed_size @read_so_far = 0 @has_returned_empty_string = false end - def sysread(number_of_bytes = nil, buf = '') + def sysread(length = nil, outbuf = '') if eof? has_returned_empty_string_val = @has_returned_empty_string @has_returned_empty_string = true @@ -15,15 +17,16 @@ def sysread(number_of_bytes = nil, buf = '') return end - if number_of_bytes.nil? || @read_so_far + number_of_bytes > @chars_to_read - number_of_bytes = @chars_to_read - @read_so_far + if length.nil? || (@read_so_far + length) > decompressed_size + length = decompressed_size - @read_so_far end - @read_so_far += number_of_bytes - @input_stream.read(number_of_bytes, buf) + + @read_so_far += length + @input_stream.read(length, outbuf) end def eof - @read_so_far >= @chars_to_read + @read_so_far >= decompressed_size end alias_method :eof?, :eof From 5707c52a1584f7c1d4f412975fe0cc8a99f4b933 Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Sun, 29 Dec 2019 13:10:25 +0100 Subject: [PATCH 070/469] Move PassTruDecompressor#decompressed_size to Decompressor --- lib/zip/decompressor.rb | 7 ++++++- lib/zip/pass_thru_decompressor.rb | 7 ++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/zip/decompressor.rb b/lib/zip/decompressor.rb index 047ed5e7..611db8b8 100644 --- a/lib/zip/decompressor.rb +++ b/lib/zip/decompressor.rb @@ -1,9 +1,14 @@ module Zip class Decompressor #:nodoc:all CHUNK_SIZE = 32_768 - def initialize(input_stream) + + attr_reader :decompressed_size + + def initialize(input_stream, decompressed_size = nil) super() + @input_stream = input_stream + @decompressed_size = decompressed_size end end end diff --git a/lib/zip/pass_thru_decompressor.rb b/lib/zip/pass_thru_decompressor.rb index 32217e07..cc49197b 100644 --- a/lib/zip/pass_thru_decompressor.rb +++ b/lib/zip/pass_thru_decompressor.rb @@ -1,10 +1,7 @@ module Zip class PassThruDecompressor < Decompressor #:nodoc:all - attr_reader :decompressed_size - - def initialize(input_stream, decompressed_size) - super(input_stream) - @decompressed_size = decompressed_size + def initialize(*args) + super @read_so_far = 0 @has_returned_empty_string = false end From cda7127107c059392387dde73600f0db60237c73 Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Wed, 8 Jan 2020 10:58:25 +0100 Subject: [PATCH 071/469] Add Decompressor#input_stream --- lib/zip/decompressor.rb | 1 + lib/zip/inflater.rb | 2 +- lib/zip/pass_thru_decompressor.rb | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/zip/decompressor.rb b/lib/zip/decompressor.rb index 611db8b8..9dfaa353 100644 --- a/lib/zip/decompressor.rb +++ b/lib/zip/decompressor.rb @@ -2,6 +2,7 @@ module Zip class Decompressor #:nodoc:all CHUNK_SIZE = 32_768 + attr_reader :input_stream attr_reader :decompressed_size def initialize(input_stream, decompressed_size = nil) diff --git a/lib/zip/inflater.rb b/lib/zip/inflater.rb index af25fa5c..734a848c 100644 --- a/lib/zip/inflater.rb +++ b/lib/zip/inflater.rb @@ -30,7 +30,7 @@ def eof def produce_input(buf = '') retried = 0 begin - @zlib_inflater.inflate(@input_stream.read(Decompressor::CHUNK_SIZE, buf)) + @zlib_inflater.inflate(input_stream.read(Decompressor::CHUNK_SIZE, buf)) rescue Zlib::BufError raise if retried >= 5 # how many times should we retry? retried += 1 diff --git a/lib/zip/pass_thru_decompressor.rb b/lib/zip/pass_thru_decompressor.rb index cc49197b..6e5f05e2 100644 --- a/lib/zip/pass_thru_decompressor.rb +++ b/lib/zip/pass_thru_decompressor.rb @@ -19,7 +19,7 @@ def sysread(length = nil, outbuf = '') end @read_so_far += length - @input_stream.read(length, outbuf) + input_stream.read(length, outbuf) end def eof From 00b525d76e295bab19b69c6f3481d60cfda9ca0f Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Sun, 5 Jan 2020 15:24:22 +0100 Subject: [PATCH 072/469] Fix returned outbuf for Inflater#sysread --- lib/zip/inflater.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/zip/inflater.rb b/lib/zip/inflater.rb index 734a848c..3904ce27 100644 --- a/lib/zip/inflater.rb +++ b/lib/zip/inflater.rb @@ -8,15 +8,15 @@ def initialize(*args) @has_returned_empty_string = false end - def sysread(length = nil, buf = '') + def sysread(length = nil, outbuf = '') while length.nil? || (@buffer.bytesize < length) break if input_finished? - @buffer << produce_input(buf) + @buffer << produce_input end return value_when_finished if eof? - @buffer.slice!(0...(length || @buffer.bytesize)) + outbuf.replace(@buffer.slice!(0...(length || @buffer.bytesize))) end def eof @@ -27,10 +27,10 @@ def eof private - def produce_input(buf = '') + def produce_input retried = 0 begin - @zlib_inflater.inflate(input_stream.read(Decompressor::CHUNK_SIZE, buf)) + @zlib_inflater.inflate(input_stream.read(Decompressor::CHUNK_SIZE)) rescue Zlib::BufError raise if retried >= 5 # how many times should we retry? retried += 1 From c66277db5885749ee9ef1594df1a9b31fdeb94e0 Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Sun, 5 Jan 2020 16:06:05 +0100 Subject: [PATCH 073/469] Rename Decompressor#sysread to #read --- lib/zip/inflater.rb | 2 +- lib/zip/input_stream.rb | 6 +++--- lib/zip/null_decompressor.rb | 2 +- lib/zip/pass_thru_decompressor.rb | 2 +- test/deflater_test.rb | 2 +- test/test_helper.rb | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/zip/inflater.rb b/lib/zip/inflater.rb index 3904ce27..d499bb99 100644 --- a/lib/zip/inflater.rb +++ b/lib/zip/inflater.rb @@ -8,7 +8,7 @@ def initialize(*args) @has_returned_empty_string = false end - def sysread(length = nil, outbuf = '') + def read(length = nil, outbuf = '') while length.nil? || (@buffer.bytesize < length) break if input_finished? @buffer << produce_input diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index e4179164..748e18c0 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -80,8 +80,8 @@ def rewind end # Modeled after IO.sysread - def sysread(number_of_bytes = nil, buf = nil) - @decompressor.sysread(number_of_bytes, buf) + def sysread(length = nil, outbuf = '') + @decompressor.read(length, outbuf) end class << self @@ -161,7 +161,7 @@ def get_decompressor end def produce_input - @decompressor.sysread(CHUNK_SIZE) + @decompressor.read(CHUNK_SIZE) end def input_finished? diff --git a/lib/zip/null_decompressor.rb b/lib/zip/null_decompressor.rb index 31ae9e11..6534b161 100644 --- a/lib/zip/null_decompressor.rb +++ b/lib/zip/null_decompressor.rb @@ -2,7 +2,7 @@ module Zip module NullDecompressor #:nodoc:all module_function - def sysread(_length = nil, _outbuf = nil) + def read(_length = nil, _outbuf = nil) nil end diff --git a/lib/zip/pass_thru_decompressor.rb b/lib/zip/pass_thru_decompressor.rb index 6e5f05e2..b5255832 100644 --- a/lib/zip/pass_thru_decompressor.rb +++ b/lib/zip/pass_thru_decompressor.rb @@ -6,7 +6,7 @@ def initialize(*args) @has_returned_empty_string = false end - def sysread(length = nil, outbuf = '') + def read(length = nil, outbuf = '') if eof? has_returned_empty_string_val = @has_returned_empty_string @has_returned_empty_string = true diff --git a/test/deflater_test.rb b/test/deflater_test.rb index b34f3570..d1970ce9 100644 --- a/test/deflater_test.rb +++ b/test/deflater_test.rb @@ -59,7 +59,7 @@ def deflate(data, fileName) def inflate(fileName) File.open(fileName, 'rb') do |file| inflater = ::Zip::Inflater.new(file) - inflater.sysread + inflater.read end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 960f71cf..224a1eb2 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -66,12 +66,12 @@ def setup end def test_read_everything - assert_equal(@refText, @decompressor.sysread) + assert_equal(@refText, @decompressor.read) end def test_read_in_chunks chunkSize = 5 - while (decompressedChunk = @decompressor.sysread(chunkSize)) + while (decompressedChunk = @decompressor.read(chunkSize)) assert_equal(@refText.slice!(0, chunkSize), decompressedChunk) end assert_equal(0, @refText.size) From 456bd4d92c995dd92cd74286bd6bdde7cc3057ef Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Sun, 5 Jan 2020 16:09:18 +0100 Subject: [PATCH 074/469] Mimic IO#read return values in Decompressor#read --- lib/zip/inflater.rb | 11 ++--------- lib/zip/pass_thru_decompressor.rb | 8 +------- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/lib/zip/inflater.rb b/lib/zip/inflater.rb index d499bb99..de3e7fc4 100644 --- a/lib/zip/inflater.rb +++ b/lib/zip/inflater.rb @@ -5,17 +5,16 @@ def initialize(*args) @buffer = ''.dup @zlib_inflater = ::Zlib::Inflate.new(-Zlib::MAX_WBITS) - @has_returned_empty_string = false end def read(length = nil, outbuf = '') + return ((length.nil? || length.zero?) ? "" : nil) if eof + while length.nil? || (@buffer.bytesize < length) break if input_finished? @buffer << produce_input end - return value_when_finished if eof? - outbuf.replace(@buffer.slice!(0...(length || @buffer.bytesize))) end @@ -43,12 +42,6 @@ def produce_input def input_finished? @zlib_inflater.finished? end - - def value_when_finished # mimic behaviour of ruby File object. - return if @has_returned_empty_string - @has_returned_empty_string = true - '' - end end end diff --git a/lib/zip/pass_thru_decompressor.rb b/lib/zip/pass_thru_decompressor.rb index b5255832..9b9a419b 100644 --- a/lib/zip/pass_thru_decompressor.rb +++ b/lib/zip/pass_thru_decompressor.rb @@ -3,16 +3,10 @@ class PassThruDecompressor < Decompressor #:nodoc:all def initialize(*args) super @read_so_far = 0 - @has_returned_empty_string = false end def read(length = nil, outbuf = '') - if eof? - has_returned_empty_string_val = @has_returned_empty_string - @has_returned_empty_string = true - return '' unless has_returned_empty_string_val - return - end + return ((length.nil? || length.zero?) ? "" : nil) if eof if length.nil? || (@read_so_far + length) > decompressed_size length = decompressed_size - @read_so_far From 2b7268373a5d9110993212c13fba03e1f8c0b532 Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Sun, 29 Dec 2019 16:07:16 +0100 Subject: [PATCH 075/469] Define compression methods --- lib/zip.rb | 2 +- lib/zip/constants.rb | 52 ++++++++++++++++++++++++++++++++++++++++++ test/constants_test.rb | 45 ++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 test/constants_test.rb diff --git a/lib/zip.rb b/lib/zip.rb index 8778556b..5261fd77 100644 --- a/lib/zip.rb +++ b/lib/zip.rb @@ -4,6 +4,7 @@ require 'fileutils' require 'stringio' require 'zlib' +require 'zip/constants' require 'zip/dos_time' require 'zip/ioextras' require 'rbconfig' @@ -29,7 +30,6 @@ require 'zip/deflater' require 'zip/streamable_stream' require 'zip/streamable_directory' -require 'zip/constants' require 'zip/errors' module Zip diff --git a/lib/zip/constants.rb b/lib/zip/constants.rb index 5eb5c1da..6feda25d 100644 --- a/lib/zip/constants.rb +++ b/lib/zip/constants.rb @@ -60,4 +60,56 @@ module Zip FSTYPE_MAC_OSX => 'Mac OS/X (Darwin)'.freeze, FSTYPE_ATHEOS => 'AtheOS'.freeze }.freeze + + COMPRESSION_METHOD_STORE = 0 + COMPRESSION_METHOD_SHRINK = 1 + COMPRESSION_METHOD_REDUCE_1 = 2 + COMPRESSION_METHOD_REDUCE_2 = 3 + COMPRESSION_METHOD_REDUCE_3 = 4 + COMPRESSION_METHOD_REDUCE_4 = 5 + COMPRESSION_METHOD_IMPLODE = 6 + # RESERVED = 7 + COMPRESSION_METHOD_DEFLATE = 8 + COMPRESSION_METHOD_DEFLATE_64 = 9 + COMPRESSION_METHOD_PKWARE_DCLI = 10 + # RESERVED = 11 + COMPRESSION_METHOD_BZIP2 = 12 + # RESERVED = 13 + COMPRESSION_METHOD_LZMA = 14 + # RESERVED = 15 + COMPRESSION_METHOD_IBM_CMPSC = 16 + # RESERVED = 17 + COMPRESSION_METHOD_IBM_TERSE = 18 + COMPRESSION_METHOD_IBM_LZ77 = 19 + COMPRESSION_METHOD_JPEG = 96 + COMPRESSION_METHOD_WAVPACK = 97 + COMPRESSION_METHOD_PPMD = 98 + COMPRESSION_METHOD_AES = 99 + + COMPRESSION_METHODS = { + COMPRESSION_METHOD_STORE => 'Store (no compression)', + COMPRESSION_METHOD_SHRINK => 'Shrink', + COMPRESSION_METHOD_REDUCE_1 => 'Reduce with compression factor 1', + COMPRESSION_METHOD_REDUCE_2 => 'Reduce with compression factor 2', + COMPRESSION_METHOD_REDUCE_3 => 'Reduce with compression factor 3', + COMPRESSION_METHOD_REDUCE_4 => 'Reduce with compression factor 4', + COMPRESSION_METHOD_IMPLODE => 'Implode', + # RESERVED = 7 + COMPRESSION_METHOD_DEFLATE => 'Deflate', + COMPRESSION_METHOD_DEFLATE_64 => 'Deflate64(tm)', + COMPRESSION_METHOD_PKWARE_DCLI => 'PKWARE Data Compression Library Imploding (old IBM TERSE)', + # RESERVED = 11 + COMPRESSION_METHOD_BZIP2 => 'BZIP2', + # RESERVED = 13 + COMPRESSION_METHOD_LZMA => 'LZMA', + # RESERVED = 15 + COMPRESSION_METHOD_IBM_CMPSC => 'IBM z/OS CMPSC Compression', + # RESERVED = 17 + COMPRESSION_METHOD_IBM_TERSE => 'IBM TERSE (new)', + COMPRESSION_METHOD_IBM_LZ77 => 'IBM LZ77 z Architecture (PFS)', + COMPRESSION_METHOD_JPEG => 'JPEG variant', + COMPRESSION_METHOD_WAVPACK => 'WavPack compressed data', + COMPRESSION_METHOD_PPMD => 'PPMd version I, Rev 1', + COMPRESSION_METHOD_AES => 'AES encryption', + }.freeze end diff --git a/test/constants_test.rb b/test/constants_test.rb new file mode 100644 index 00000000..8be01715 --- /dev/null +++ b/test/constants_test.rb @@ -0,0 +1,45 @@ +require 'test_helper' + +class ConstantsTest < MiniTest::Test + def test_compression_methods + assert_equal(0, Zip::COMPRESSION_METHOD_STORE) + assert_equal(1, Zip::COMPRESSION_METHOD_SHRINK) + assert_equal(2, Zip::COMPRESSION_METHOD_REDUCE_1) + assert_equal(3, Zip::COMPRESSION_METHOD_REDUCE_2) + assert_equal(4, Zip::COMPRESSION_METHOD_REDUCE_3) + assert_equal(5, Zip::COMPRESSION_METHOD_REDUCE_4) + assert_equal(6, Zip::COMPRESSION_METHOD_IMPLODE) + assert_equal(8, Zip::COMPRESSION_METHOD_DEFLATE) + assert_equal(9, Zip::COMPRESSION_METHOD_DEFLATE_64) + assert_equal(10, Zip::COMPRESSION_METHOD_PKWARE_DCLI) + assert_equal(12, Zip::COMPRESSION_METHOD_BZIP2) + assert_equal(14, Zip::COMPRESSION_METHOD_LZMA) + assert_equal(16, Zip::COMPRESSION_METHOD_IBM_CMPSC) + assert_equal(18, Zip::COMPRESSION_METHOD_IBM_TERSE) + assert_equal(19, Zip::COMPRESSION_METHOD_IBM_LZ77) + assert_equal(96, Zip::COMPRESSION_METHOD_JPEG) + assert_equal(97, Zip::COMPRESSION_METHOD_WAVPACK) + assert_equal(98, Zip::COMPRESSION_METHOD_PPMD) + assert_equal(99, Zip::COMPRESSION_METHOD_AES) + + assert(Zip::COMPRESSION_METHODS[Zip::COMPRESSION_METHOD_STORE]) + assert(Zip::COMPRESSION_METHODS[Zip::COMPRESSION_METHOD_SHRINK]) + assert(Zip::COMPRESSION_METHODS[Zip::COMPRESSION_METHOD_REDUCE_1]) + assert(Zip::COMPRESSION_METHODS[Zip::COMPRESSION_METHOD_REDUCE_2]) + assert(Zip::COMPRESSION_METHODS[Zip::COMPRESSION_METHOD_REDUCE_3]) + assert(Zip::COMPRESSION_METHODS[Zip::COMPRESSION_METHOD_REDUCE_4]) + assert(Zip::COMPRESSION_METHODS[Zip::COMPRESSION_METHOD_IMPLODE]) + assert(Zip::COMPRESSION_METHODS[Zip::COMPRESSION_METHOD_DEFLATE]) + assert(Zip::COMPRESSION_METHODS[Zip::COMPRESSION_METHOD_DEFLATE_64]) + assert(Zip::COMPRESSION_METHODS[Zip::COMPRESSION_METHOD_PKWARE_DCLI]) + assert(Zip::COMPRESSION_METHODS[Zip::COMPRESSION_METHOD_BZIP2]) + assert(Zip::COMPRESSION_METHODS[Zip::COMPRESSION_METHOD_LZMA]) + assert(Zip::COMPRESSION_METHODS[Zip::COMPRESSION_METHOD_IBM_CMPSC]) + assert(Zip::COMPRESSION_METHODS[Zip::COMPRESSION_METHOD_IBM_TERSE]) + assert(Zip::COMPRESSION_METHODS[Zip::COMPRESSION_METHOD_IBM_LZ77]) + assert(Zip::COMPRESSION_METHODS[Zip::COMPRESSION_METHOD_JPEG]) + assert(Zip::COMPRESSION_METHODS[Zip::COMPRESSION_METHOD_WAVPACK]) + assert(Zip::COMPRESSION_METHODS[Zip::COMPRESSION_METHOD_PPMD]) + assert(Zip::COMPRESSION_METHODS[Zip::COMPRESSION_METHOD_AES]) + end +end From a5d068d3e8c8eb4dc9ce38ee2f6e9cb3e5dee796 Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Fri, 20 Dec 2019 20:01:53 +0100 Subject: [PATCH 076/469] Support Decompressor plugins --- lib/zip/decompressor.rb | 12 ++++++++++++ lib/zip/inflater.rb | 2 ++ lib/zip/input_stream.rb | 14 ++++++++------ lib/zip/pass_thru_decompressor.rb | 2 ++ test/decompressor_test.rb | 15 +++++++++++++++ 5 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 test/decompressor_test.rb diff --git a/lib/zip/decompressor.rb b/lib/zip/decompressor.rb index 9dfaa353..2f89545c 100644 --- a/lib/zip/decompressor.rb +++ b/lib/zip/decompressor.rb @@ -2,6 +2,18 @@ module Zip class Decompressor #:nodoc:all CHUNK_SIZE = 32_768 + def self.decompressor_classes + @decompressor_classes ||= {} + end + + def self.register(compression_method, decompressor_class) + decompressor_classes[compression_method] = decompressor_class + end + + def self.find_by_compression_method(compression_method) + decompressor_classes[compression_method] + end + attr_reader :input_stream attr_reader :decompressed_size diff --git a/lib/zip/inflater.rb b/lib/zip/inflater.rb index de3e7fc4..4e217f6f 100644 --- a/lib/zip/inflater.rb +++ b/lib/zip/inflater.rb @@ -43,6 +43,8 @@ def input_finished? @zlib_inflater.finished? end end + + ::Zip::Decompressor.register(::Zip::COMPRESSION_METHOD_DEFLATE, ::Zip::Inflater) end # Copyright (C) 2002, 2003 Thomas Sondergaard diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index 748e18c0..b4c502f5 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -146,18 +146,20 @@ def get_decrypted_io def get_decompressor return ::Zip::NullDecompressor if @current_entry.nil? - if @current_entry.compression_method == ::Zip::Entry::STORED + decompressed_size = if @current_entry.incomplete? && @current_entry.crc == 0 && @current_entry.size == 0 && @complete_entry - ::Zip::PassThruDecompressor.new(@decrypted_io, @complete_entry.size) + @complete_entry.size else - ::Zip::PassThruDecompressor.new(@decrypted_io, @current_entry.size) + @current_entry.size end - elsif @current_entry.compression_method == ::Zip::Entry::DEFLATED - ::Zip::Inflater.new(@decrypted_io) - else + + decompressor_class = ::Zip::Decompressor.find_by_compression_method(@current_entry.compression_method) + if decompressor_class.nil? raise ::Zip::CompressionMethodError, "Unsupported compression method #{@current_entry.compression_method}" end + + decompressor_class.new(@decrypted_io, decompressed_size) end def produce_input diff --git a/lib/zip/pass_thru_decompressor.rb b/lib/zip/pass_thru_decompressor.rb index 9b9a419b..ac21b61e 100644 --- a/lib/zip/pass_thru_decompressor.rb +++ b/lib/zip/pass_thru_decompressor.rb @@ -22,6 +22,8 @@ def eof alias_method :eof?, :eof end + + ::Zip::Decompressor.register(::Zip::COMPRESSION_METHOD_STORE, ::Zip::PassThruDecompressor) end # Copyright (C) 2002, 2003 Thomas Sondergaard diff --git a/test/decompressor_test.rb b/test/decompressor_test.rb new file mode 100644 index 00000000..d7ff2e73 --- /dev/null +++ b/test/decompressor_test.rb @@ -0,0 +1,15 @@ +require 'test_helper' +class DecompressorTest < MiniTest::Test + TEST_COMPRESSION_METHOD = 255 + + class TestCompressionClass + end + + def test_decompressor_registration + assert_nil(::Zip::Decompressor.find_by_compression_method(TEST_COMPRESSION_METHOD)) + + ::Zip::Decompressor.register(TEST_COMPRESSION_METHOD, TestCompressionClass) + + assert_equal(TestCompressionClass, ::Zip::Decompressor.find_by_compression_method(TEST_COMPRESSION_METHOD)) + end +end From 0b9433c3b26c8695376eb3751c26731b8f0839f0 Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Sun, 5 Jan 2020 19:49:12 +0100 Subject: [PATCH 077/469] Add test for unsupported decompression, e.g bzip2 --- test/bzip2_support_test.rb | 11 +++++++++++ test/data/zipWithBzip2Compression.zip | Bin 0 -> 9259 bytes 2 files changed, 11 insertions(+) create mode 100644 test/bzip2_support_test.rb create mode 100644 test/data/zipWithBzip2Compression.zip diff --git a/test/bzip2_support_test.rb b/test/bzip2_support_test.rb new file mode 100644 index 00000000..ab86b4e8 --- /dev/null +++ b/test/bzip2_support_test.rb @@ -0,0 +1,11 @@ +require 'test_helper' + +class Bzip2SupportTest < MiniTest::Test + BZIP2_ZIP_TEST_FILE = 'test/data/zipWithBzip2Compression.zip' + + def test_read + Zip::InputStream.open(BZIP2_ZIP_TEST_FILE) do |zis| + assert_raises(Zip::CompressionMethodError) { zis.get_next_entry } + end + end +end diff --git a/test/data/zipWithBzip2Compression.zip b/test/data/zipWithBzip2Compression.zip new file mode 100644 index 0000000000000000000000000000000000000000..1cd268b31eb9c26dc3120c29e1f0981b96cb59a4 GIT binary patch literal 9259 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 Ti}#;s5tKfC_{*p~EA literal 0 HcmV?d00001 From 040962a59fd0170ef1e993a1fd2634cf039e7897 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Sat, 1 Feb 2020 13:19:15 +0000 Subject: [PATCH 078/469] Remove unused error argument --- lib/zip/inflater.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zip/inflater.rb b/lib/zip/inflater.rb index 4e217f6f..bb735d11 100644 --- a/lib/zip/inflater.rb +++ b/lib/zip/inflater.rb @@ -35,7 +35,7 @@ def produce_input retried += 1 retry end - rescue Zlib::Error => e + rescue Zlib::Error raise(::Zip::DecompressionError, 'zlib error while inflating') end From f42827e99c7018aba05a99965a64531f830e4e8b Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Sat, 1 Feb 2020 13:24:50 +0000 Subject: [PATCH 079/469] Bump version to 2.2.0 --- Changelog.md | 4 ++++ lib/zip/version.rb | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 0d12fb3b..22cdef98 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,9 @@ # X.X.X (Next) +# 2.2.0 (2020-02-01) + +- Add support for decompression plugin gems [#427](https://github.com/rubyzip/rubyzip/pull/427) + # 2.1.0 (2020-01-25) - Fix (at least partially) the `restore_times` and `restore_permissions` options to `Zip::File.new` [#413](https://github.com/rubyzip/rubyzip/pull/413) diff --git a/lib/zip/version.rb b/lib/zip/version.rb index 2af7a65c..0955ae03 100644 --- a/lib/zip/version.rb +++ b/lib/zip/version.rb @@ -1,3 +1,3 @@ module Zip - VERSION = '2.1.0' + VERSION = '2.2.0' end From 609c12d1c484d04e056dc3e39a47cd45a887f1dd Mon Sep 17 00:00:00 2001 From: taichi Date: Wed, 5 Feb 2020 11:11:46 +0900 Subject: [PATCH 080/469] added Ruby 2.7 to CI regression --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 21a4c64f..b903c3b3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ rvm: - 2.4 - 2.5 - 2.6 + - 2.7 - ruby-head matrix: fast_finish: true From 3bc85ccdecfb9be015ecde2227fe9db202c7ea0d Mon Sep 17 00:00:00 2001 From: taichi Date: Wed, 5 Feb 2020 11:30:30 +0900 Subject: [PATCH 081/469] fixed frozen error caseud by frozen string literal --- lib/zip/crypto/decrypted_io.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zip/crypto/decrypted_io.rb b/lib/zip/crypto/decrypted_io.rb index ec9cab8b..362e4a49 100644 --- a/lib/zip/crypto/decrypted_io.rb +++ b/lib/zip/crypto/decrypted_io.rb @@ -7,7 +7,7 @@ def initialize(io, decrypter) @decrypter = decrypter end - def read(length = nil, outbuf = '') + def read(length = nil, outbuf = +'') return ((length.nil? || length.zero?) ? "" : nil) if eof while length.nil? || (buffer.bytesize < length) From b326d1743890b7429472c06403f5294e18d38c34 Mon Sep 17 00:00:00 2001 From: taichi Date: Wed, 5 Feb 2020 11:40:56 +0900 Subject: [PATCH 082/469] use @+ operator instead of #dup to get unfrozen string --- lib/zip/crypto/decrypted_io.rb | 2 +- lib/zip/entry.rb | 2 +- lib/zip/extra_field.rb | 2 +- lib/zip/file.rb | 2 +- lib/zip/inflater.rb | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/zip/crypto/decrypted_io.rb b/lib/zip/crypto/decrypted_io.rb index 362e4a49..1dab17c0 100644 --- a/lib/zip/crypto/decrypted_io.rb +++ b/lib/zip/crypto/decrypted_io.rb @@ -25,7 +25,7 @@ def eof end def buffer - @buffer ||= ''.dup + @buffer ||= +'' end def input_finished? diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index f1963d8d..a24fb791 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -615,7 +615,7 @@ def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exi get_input_stream do |is| bytes_written = 0 warned = false - buf = ''.dup + buf = +'' while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf)) os << buf bytes_written += buf.bytesize diff --git a/lib/zip/extra_field.rb b/lib/zip/extra_field.rb index 72c36764..0dcf0d5e 100644 --- a/lib/zip/extra_field.rb +++ b/lib/zip/extra_field.rb @@ -26,7 +26,7 @@ def extra_field_type_unknown(binstr, len, i) end def create_unknown_item - s = ''.dup + s = +'' class << s alias_method :to_c_dir_bin, :to_s alias_method :to_local_bin, :to_s diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 45017822..1479de26 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -402,7 +402,7 @@ def get_entry(entry) # Creates a directory def mkdir(entryName, permissionInt = 0o755) raise Errno::EEXIST, "File exists - #{entryName}" if find_entry(entryName) - entryName = entryName.dup.to_s + entryName = +entryName.to_s entryName << '/' unless entryName.end_with?('/') @entry_set << ::Zip::StreamableDirectory.new(@name, entryName, nil, permissionInt) end diff --git a/lib/zip/inflater.rb b/lib/zip/inflater.rb index bb735d11..c18f245c 100644 --- a/lib/zip/inflater.rb +++ b/lib/zip/inflater.rb @@ -3,7 +3,7 @@ class Inflater < Decompressor #:nodoc:all def initialize(*args) super - @buffer = ''.dup + @buffer = +'' @zlib_inflater = ::Zlib::Inflate.new(-Zlib::MAX_WBITS) end From 976dbd34081af9b0c8c1970410e787d32fb7cc5c Mon Sep 17 00:00:00 2001 From: taichi Date: Sat, 8 Feb 2020 19:51:47 +0900 Subject: [PATCH 083/469] reverted the change according to comment below: https://github.com/rubyzip/rubyzip/pull/431#discussion_r376698387 --- lib/zip/file.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 1479de26..45017822 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -402,7 +402,7 @@ def get_entry(entry) # Creates a directory def mkdir(entryName, permissionInt = 0o755) raise Errno::EEXIST, "File exists - #{entryName}" if find_entry(entryName) - entryName = +entryName.to_s + entryName = entryName.dup.to_s entryName << '/' unless entryName.end_with?('/') @entry_set << ::Zip::StreamableDirectory.new(@name, entryName, nil, permissionInt) end From 247fd432901b143ed37c11a90da3c6c0f01489b8 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Sun, 9 Feb 2020 08:24:30 +0000 Subject: [PATCH 084/469] Update changelog for #431 --- Changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changelog.md b/Changelog.md index 22cdef98..a42b8320 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,7 @@ # X.X.X (Next) +- Fix frozen string literal error [#431](https://github.com/rubyzip/rubyzip/pull/431) + # 2.2.0 (2020-02-01) - Add support for decompression plugin gems [#427](https://github.com/rubyzip/rubyzip/pull/427) From 862892c0ac47af43ed8df6f9dd3f6b751dfbdf38 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 30 Oct 2019 08:49:05 +0000 Subject: [PATCH 085/469] Quickly fire up a console with 'zip' pre-loaded. --- bin/console | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100755 bin/console diff --git a/bin/console b/bin/console new file mode 100755 index 00000000..6df9a590 --- /dev/null +++ b/bin/console @@ -0,0 +1,7 @@ +#!/usr/bin/env ruby + +require 'bundler/setup' +require 'zip' + +require 'irb' +IRB.start(__FILE__) From c8bfd147643cf783c779ec6c770077c8394353a5 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 14 Sep 2019 15:38:27 +0100 Subject: [PATCH 086/469] Update rubocop version and the config files. Also rename .rubocop_rubyzip.yml to be consistent with Rubocop default setup. --- .rubocop.yml | 7 +- .rubocop_rubyzip.yml | 137 --------- .rubocop_todo.yml | 586 ++++++++++++++++++++++++++++++++++++ rubyzip.gemspec | 2 +- test/path_traversal_test.rb | 2 +- 5 files changed, 589 insertions(+), 145 deletions(-) delete mode 100644 .rubocop_rubyzip.yml create mode 100644 .rubocop_todo.yml diff --git a/.rubocop.yml b/.rubocop.yml index a408fa0d..cc32da4b 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,6 +1 @@ -inherit_from: - - .rubocop_rubyzip.yml -AllCops: - TargetRubyVersion: 1.9 -Style/MutableConstant: - Enabled: false # Because some existent code relies on mutable constant +inherit_from: .rubocop_todo.yml diff --git a/.rubocop_rubyzip.yml b/.rubocop_rubyzip.yml deleted file mode 100644 index 3030f8a0..00000000 --- a/.rubocop_rubyzip.yml +++ /dev/null @@ -1,137 +0,0 @@ -# This configuration was generated by `rubocop --auto-gen-config` -# on 2015-06-08 10:22:52 +0300 using RuboCop version 0.32.0. -# The point is for the user to remove these configuration records -# one by one as the offenses are removed from the code base. -# Note that changes in the inspected code, or installation of new -# versions of RuboCop, may require this file to be generated again. - -# Offense count: 13 -Lint/HandleExceptions: - Enabled: false - -# Offense count: 1 -Lint/LiteralInCondition: - Enabled: false - -# Offense count: 1 -Lint/RescueException: - Enabled: false - -# Offense count: 1 -Lint/UselessComparison: - Enabled: false - -# Offense count: 115 -Metrics/AbcSize: - Max: 62 - -# Offense count: 12 -# Configuration parameters: CountComments. -Metrics/ClassLength: - Max: 562 - -# Offense count: 21 -Metrics/CyclomaticComplexity: - Max: 14 - -# Offense count: 237 -# Configuration parameters: AllowURI, URISchemes. -Metrics/LineLength: - Max: 236 - -# Offense count: 108 -# Configuration parameters: CountComments. -Metrics/MethodLength: - Max: 35 - -# Offense count: 2 -# Configuration parameters: CountKeywordArgs. -Metrics/ParameterLists: - Max: 10 - -# Offense count: 15 -Metrics/PerceivedComplexity: - Max: 15 - -# Offense count: 8 -Style/AccessorMethodName: - Enabled: false - -# Offense count: 23 -# Cop supports --auto-correct. -Style/Alias: - Enabled: false - -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles, ProceduralMethods, FunctionalMethods, IgnoredMethods. -Style/BlockDelimiters: - Enabled: false - -# Offense count: 7 -# Configuration parameters: EnforcedStyle, SupportedStyles. -Style/ClassAndModuleChildren: - Enabled: false - -# Offense count: 15 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. -Style/ClassCheck: - Enabled: false - -# Offense count: 77 -Style/Documentation: - Enabled: false - -# Offense count: 1 -# Cop supports --auto-correct. -Style/InfiniteLoop: - Enabled: false - -# Offense count: 1 -Style/ModuleFunction: - Enabled: false - -# Offense count: 1 -Style/MultilineBlockChain: - Enabled: false - -# Offense count: 3 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes. -Style/RegexpLiteral: - Enabled: false - -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: AllowAsExpressionSeparator. -Style/Semicolon: - Enabled: false - -# Offense count: 79 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. -Style/SignalException: - Enabled: false - -# Offense count: 9 -# Cop supports --auto-correct. -# Configuration parameters: MultiSpaceAllowedForOperators. -Style/SpaceAroundOperators: - Enabled: false - -# Offense count: 30 -# Cop supports --auto-correct. -Style/SpecialGlobalVars: - Enabled: false - -# Offense count: 22 -# Cop supports --auto-correct. -# Configuration parameters: IgnoredMethods. -Style/SymbolProc: - Enabled: false - -# Offense count: 151 -# Configuration parameters: EnforcedStyle, SupportedStyles. -Style/VariableName: - Enabled: false diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 00000000..daa76c02 --- /dev/null +++ b/.rubocop_todo.yml @@ -0,0 +1,586 @@ +# This configuration was generated by +# `rubocop --auto-gen-config` +# on 2020-02-08 14:58:51 +0000 using RuboCop version 0.79.0. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +# Offense count: 3 +# Cop supports --auto-correct. +# Configuration parameters: TreatCommentsAsGroupSeparators, Include. +# Include: **/*.gemspec +Gemspec/OrderedDependencies: + Exclude: + - 'rubyzip.gemspec' + +# Offense count: 76 +# Cop supports --auto-correct. +Layout/EmptyLineAfterGuardClause: + Enabled: false + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines, beginning_only, ending_only +Layout/EmptyLinesAroundClassBody: + Exclude: + - 'test/extra_field_ut_test.rb' + +# Offense count: 26 +# Cop supports --auto-correct. +# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. +# SupportedHashRocketStyles: key, separator, table +# SupportedColonStyles: key, separator, table +# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit +Layout/HashAlignment: + Exclude: + - 'lib/zip/constants.rb' + - 'lib/zip/file.rb' + - 'rubyzip.gemspec' + +# Offense count: 5 +# Cop supports --auto-correct. +# Configuration parameters: AllowForAlignment, EnforcedStyleForExponentOperator. +# SupportedStylesForExponentOperator: space, no_space +Layout/SpaceAroundOperators: + Exclude: + - 'lib/zip/entry.rb' + - 'lib/zip/extra_field/zip64.rb' + - 'lib/zip/file.rb' + - 'lib/zip/filesystem.rb' + - 'lib/zip/input_stream.rb' + +# Offense count: 1 +Lint/AmbiguousBlockAssociation: + Exclude: + - 'test/filesystem/file_nonmutating_test.rb' + +# Offense count: 1 +Lint/LiteralAsCondition: + Exclude: + - 'lib/zip/ioextras/abstract_input_stream.rb' + +# Offense count: 1 +Lint/RescueException: + Exclude: + - 'test/output_stream_test.rb' + +# Offense count: 12 +# Configuration parameters: AllowComments. +Lint/SuppressedException: + Exclude: + - 'lib/zip/ioextras/abstract_input_stream.rb' + - 'test/central_directory_entry_test.rb' + - 'test/central_directory_test.rb' + - 'test/errors_test.rb' + - 'test/ioextras/abstract_input_stream_test.rb' + - 'test/local_entry_test.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. +Lint/UnusedBlockArgument: + Exclude: + - 'test/file_test.rb' + +# Offense count: 1 +# Configuration parameters: ContextCreatingMethods, MethodCreatingMethods. +Lint/UselessAccessModifier: + Exclude: + - 'lib/zip/entry.rb' + +# Offense count: 1 +Lint/UselessComparison: + Exclude: + - 'test/entry_test.rb' + +# Offense count: 120 +Metrics/AbcSize: + Max: 60 + +# Offense count: 3 +# Configuration parameters: CountComments, ExcludedMethods. +# ExcludedMethods: refine +Metrics/BlockLength: + Max: 43 + +# Offense count: 15 +# Configuration parameters: CountComments. +Metrics/ClassLength: + Max: 579 + +# Offense count: 26 +Metrics/CyclomaticComplexity: + Max: 14 + +# Offense count: 120 +# Configuration parameters: CountComments, ExcludedMethods. +Metrics/MethodLength: + Max: 45 + +# Offense count: 2 +# Configuration parameters: CountKeywordArgs. +Metrics/ParameterLists: + Max: 10 + +# Offense count: 21 +Metrics/PerceivedComplexity: + Max: 15 + +# Offense count: 9 +Naming/AccessorMethodName: + Exclude: + - 'lib/zip/entry.rb' + - 'lib/zip/filesystem.rb' + - 'lib/zip/input_stream.rb' + - 'lib/zip/streamable_stream.rb' + - 'test/file_permissions_test.rb' + +# Offense count: 18 +# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. +Naming/BlockParameterName: + Exclude: + - 'lib/zip/file.rb' + - 'lib/zip/filesystem.rb' + - 'samples/zipfind.rb' + - 'test/central_directory_test.rb' + - 'test/file_extract_directory_test.rb' + - 'test/file_extract_test.rb' + - 'test/output_stream_test.rb' + - 'test/test_helper.rb' + +# Offense count: 2 +# Configuration parameters: EnforcedStyle. +# SupportedStyles: lowercase, uppercase +Naming/HeredocDelimiterCase: + Exclude: + - 'lib/zip/filesystem.rb' + - 'test/test_helper.rb' + +# Offense count: 2 +# Configuration parameters: EnforcedStyleForLeadingUnderscores. +# SupportedStylesForLeadingUnderscores: disallowed, required, optional +Naming/MemoizedInstanceVariableName: + Exclude: + - 'lib/zip/extra_field/old_unix.rb' + - 'lib/zip/extra_field/unix.rb' + +# Offense count: 140 +# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. +# AllowedNames: io, id, to, by, on, in, at, ip, db, os +Naming/MethodParameterName: + Enabled: false + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: PreferredName. +Naming/RescuedExceptionsVariableName: + Exclude: + - 'samples/zipfind.rb' + +# Offense count: 721 +# Configuration parameters: EnforcedStyle. +# SupportedStyles: snake_case, camelCase +Naming/VariableName: + Enabled: false + +# Offense count: 13 +Security/Open: + Exclude: + - 'lib/zip/file.rb' + - 'lib/zip/filesystem.rb' + - 'lib/zip/input_stream.rb' + - 'test/encryption_test.rb' + - 'test/stored_support_test.rb' + +# Offense count: 7 +# Configuration parameters: EnforcedStyle. +# SupportedStyles: inline, group +Style/AccessModifierDeclarations: + Exclude: + - 'lib/zip/central_directory.rb' + - 'lib/zip/extra_field/zip64.rb' + - 'lib/zip/filesystem.rb' + +# Offense count: 4 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: prefer_alias, prefer_alias_method +Style/Alias: + Exclude: + - 'lib/zip/inflater.rb' + - 'lib/zip/ioextras/abstract_input_stream.rb' + - 'lib/zip/pass_thru_decompressor.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, IgnoredMethods, AllowBracesOnProceduralOneLiners. +# SupportedStyles: line_count_based, semantic, braces_for_chaining, always_braces +# ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object +# FunctionalMethods: let, let!, subject, watch +# IgnoredMethods: lambda, proc, it +Style/BlockDelimiters: + Exclude: + - 'test/case_sensitivity_test.rb' + +# Offense count: 7 +# Cop supports --auto-correct. +# Configuration parameters: AutoCorrect, EnforcedStyle. +# SupportedStyles: nested, compact +Style/ClassAndModuleChildren: + Exclude: + - 'lib/zip/extra_field/generic.rb' + - 'lib/zip/extra_field/ntfs.rb' + - 'lib/zip/extra_field/old_unix.rb' + - 'lib/zip/extra_field/universal_time.rb' + - 'lib/zip/extra_field/unix.rb' + - 'lib/zip/extra_field/zip64.rb' + - 'lib/zip/extra_field/zip64_placeholder.rb' + +# Offense count: 15 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: is_a?, kind_of? +Style/ClassCheck: + Exclude: + - 'lib/zip/central_directory.rb' + - 'lib/zip/entry_set.rb' + - 'lib/zip/file.rb' + - 'lib/zip/output_stream.rb' + - 'test/filesystem/file_nonmutating_test.rb' + - 'test/ioextras/fake_io_test.rb' + - 'test/output_stream_test.rb' + +# Offense count: 2 +Style/CommentedKeyword: + Exclude: + - 'lib/zip/ioextras.rb' + - 'lib/zip/streamable_stream.rb' + +# Offense count: 26 +Style/Documentation: + Enabled: false + +# Offense count: 3 +# Cop supports --auto-correct. +Style/Encoding: + Exclude: + - 'rubyzip.gemspec' + - 'test/errors_test.rb' + - 'test/unicode_file_names_and_comments_test.rb' + +# Offense count: 2 +Style/EvalWithLocation: + Exclude: + - 'test/test_helper.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +Style/ExpandPathArguments: + Exclude: + - 'rubyzip.gemspec' + +# Offense count: 1 +# Configuration parameters: EnforcedStyle. +# SupportedStyles: left_coerce, right_coerce, single_coerce, fdiv +Style/FloatDivision: + Exclude: + - 'samples/example.rb' + +# Offense count: 3 +# Configuration parameters: . +# SupportedStyles: annotated, template, unannotated +Style/FormatStringToken: + EnforcedStyle: unannotated + +# Offense count: 95 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: always, never +Style/FrozenStringLiteralComment: + Enabled: false + +# Offense count: 8 +# Configuration parameters: MinBodyLength. +Style/GuardClause: + Exclude: + - 'lib/zip/entry.rb' + - 'lib/zip/extra_field/generic.rb' + - 'samples/zipfind.rb' + - 'test/test_helper.rb' + +# Offense count: 1 +# Configuration parameters: AllowIfModifier. +Style/IfInsideElse: + Exclude: + - 'lib/zip/entry.rb' + +# Offense count: 17 +# Cop supports --auto-correct. +Style/IfUnlessModifier: + Exclude: + - 'lib/zip/entry.rb' + - 'lib/zip/extra_field/generic.rb' + - 'lib/zip/file.rb' + - 'lib/zip/filesystem.rb' + - 'lib/zip/input_stream.rb' + - 'lib/zip/pass_thru_decompressor.rb' + - 'lib/zip/streamable_stream.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +Style/InfiniteLoop: + Exclude: + - 'lib/zip/ioextras/abstract_input_stream.rb' + +# Offense count: 1 +Style/MixinUsage: + Exclude: + - 'samples/write_simple.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, Autocorrect. +# SupportedStyles: module_function, extend_self +Style/ModuleFunction: + Exclude: + - 'lib/zip.rb' + +# Offense count: 1 +Style/MultilineBlockChain: + Exclude: + - 'lib/zip/crypto/traditional_encryption.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +Style/MultilineWhenThen: + Exclude: + - 'lib/zip/output_stream.rb' + +# Offense count: 56 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: literals, strict +Style/MutableConstant: + Enabled: false + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, MinBodyLength. +# SupportedStyles: skip_modifier_ifs, always +Style/Next: + Exclude: + - 'lib/zip/entry.rb' + +# Offense count: 7 +# Cop supports --auto-correct. +# Configuration parameters: IncludeSemanticChanges. +Style/NonNilCheck: + Exclude: + - 'test/case_sensitivity_test.rb' + - 'test/file_test.rb' + - 'test/settings_test.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedOctalStyle. +# SupportedOctalStyles: zero_with_o, zero_only +Style/NumericLiteralPrefix: + Exclude: + - 'test/file_options_test.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: Strict. +Style/NumericLiterals: + MinDigits: 6 + +# Offense count: 23 +# Cop supports --auto-correct. +# Configuration parameters: AutoCorrect, EnforcedStyle, IgnoredMethods. +# SupportedStyles: predicate, comparison +Style/NumericPredicate: + Exclude: + - 'spec/**/*' + - 'lib/zip/entry.rb' + - 'lib/zip/extra_field/old_unix.rb' + - 'lib/zip/extra_field/universal_time.rb' + - 'lib/zip/extra_field/unix.rb' + - 'lib/zip/file.rb' + - 'lib/zip/filesystem.rb' + - 'lib/zip/input_stream.rb' + - 'lib/zip/ioextras.rb' + - 'lib/zip/ioextras/abstract_input_stream.rb' + - 'test/file_split_test.rb' + - 'test/test_helper.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +Style/OrAssignment: + Exclude: + - 'test/test_helper.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: AllowMultipleReturnValues. +Style/RedundantReturn: + Exclude: + - 'lib/zip/central_directory.rb' + +# Offense count: 12 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, AllowInnerSlashes. +# SupportedStyles: slashes, percent_r, mixed +Style/RegexpLiteral: + Exclude: + - 'lib/zip/filesystem.rb' + - 'test/entry_test.rb' + - 'test/path_traversal_test.rb' + - 'test/settings_test.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: implicit, explicit +Style/RescueStandardError: + Exclude: + - 'test/gentestfiles.rb' + +# Offense count: 17 +# Cop supports --auto-correct. +# Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods. +# AllowedMethods: present?, blank?, presence, try, try! +Style/SafeNavigation: + Exclude: + - 'lib/zip/entry.rb' + - 'lib/zip/input_stream.rb' + - 'lib/zip/output_stream.rb' + - 'test/file_extract_test.rb' + - 'test/filesystem/file_nonmutating_test.rb' + - 'test/filesystem/file_stat_test.rb' + - 'test/test_helper.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: AllowAsExpressionSeparator. +Style/Semicolon: + Exclude: + - 'test/file_test.rb' + +# Offense count: 8 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: only_raise, only_fail, semantic +Style/SignalException: + Exclude: + - 'test/central_directory_entry_test.rb' + - 'test/central_directory_test.rb' + - 'test/filesystem/file_nonmutating_test.rb' + - 'test/ioextras/abstract_input_stream_test.rb' + - 'test/local_entry_test.rb' + - 'test/test_helper.rb' + +# Offense count: 30 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: use_perl_names, use_english_names +Style/SpecialGlobalVars: + Exclude: + - 'lib/zip/filesystem.rb' + - 'lib/zip/ioextras/abstract_input_stream.rb' + - 'lib/zip/ioextras/abstract_output_stream.rb' + - 'samples/example.rb' + - 'samples/example_filesystem.rb' + - 'samples/gtk_ruby_zip.rb' + - 'samples/qtzip.rb' + - 'samples/write_simple.rb' + - 'samples/zipfind.rb' + - 'test/gentestfiles.rb' + - 'test/ioextras/abstract_input_stream_test.rb' + - 'test/ioextras/abstract_output_stream_test.rb' + - 'test/output_stream_test.rb' + - 'test/test_helper.rb' + +# Offense count: 4 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline. +# SupportedStyles: single_quotes, double_quotes +Style/StringLiterals: + Exclude: + - 'lib/zip/crypto/decrypted_io.rb' + - 'lib/zip/inflater.rb' + - 'lib/zip/pass_thru_decompressor.rb' + - 'test/file_test.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: MinSize. +# SupportedStyles: percent, brackets +Style/SymbolArray: + EnforcedStyle: brackets + +# Offense count: 42 +# Cop supports --auto-correct. +# Configuration parameters: IgnoredMethods. +# IgnoredMethods: respond_to, define_method +Style/SymbolProc: + Exclude: + - 'lib/zip/file.rb' + - 'lib/zip/filesystem.rb' + - 'test/basic_zip_file_test.rb' + - 'test/case_sensitivity_test.rb' + - 'test/deflater_test.rb' + - 'test/file_extract_test.rb' + - 'test/file_split_test.rb' + - 'test/file_test.rb' + - 'test/filesystem/file_nonmutating_test.rb' + +# Offense count: 3 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, AllowSafeAssignment. +# SupportedStyles: require_parentheses, require_no_parentheses, require_parentheses_when_complex +Style/TernaryParentheses: + Exclude: + - 'lib/zip/crypto/decrypted_io.rb' + - 'lib/zip/inflater.rb' + - 'lib/zip/pass_thru_decompressor.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyleForMultiline. +# SupportedStylesForMultiline: comma, consistent_comma, no_comma +Style/TrailingCommaInArrayLiteral: + Exclude: + - 'lib/zip/central_directory.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyleForMultiline. +# SupportedStylesForMultiline: comma, consistent_comma, no_comma +Style/TrailingCommaInHashLiteral: + Exclude: + - 'lib/zip/constants.rb' + +# Offense count: 6 +# Cop supports --auto-correct. +Style/UnpackFirst: + Exclude: + - 'lib/zip/entry.rb' + - 'lib/zip/extra_field.rb' + - 'lib/zip/extra_field/generic.rb' + - 'lib/zip/extra_field/zip64.rb' + +# Offense count: 3 +# Cop supports --auto-correct. +Style/ZeroLengthPredicate: + Exclude: + - 'lib/zip/file.rb' + - 'lib/zip/input_stream.rb' + +# Offense count: 247 +# Cop supports --auto-correct. +# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. +# URISchemes: http, https +Layout/LineLength: + Max: 236 diff --git a/rubyzip.gemspec b/rubyzip.gemspec index f8c59a18..30dd2998 100644 --- a/rubyzip.gemspec +++ b/rubyzip.gemspec @@ -27,5 +27,5 @@ Gem::Specification.new do |s| s.add_development_dependency 'pry', '~> 0.10' s.add_development_dependency 'minitest', '~> 5.4' s.add_development_dependency 'coveralls', '~> 0.7' - s.add_development_dependency 'rubocop', '~> 0.49.1' + s.add_development_dependency 'rubocop', '~> 0.79' end diff --git a/test/path_traversal_test.rb b/test/path_traversal_test.rb index 8b6f67d5..6950fbcb 100644 --- a/test/path_traversal_test.rb +++ b/test/path_traversal_test.rb @@ -64,7 +64,7 @@ def test_non_leading_dot_dot_with_existing_folder entries = { 'tmp/' => '', 'tmp/../../moo' => /WARNING: skipped \'tmp\/\.\.\/\.\.\/moo\'/ - } + } in_tmpdir do extract_paths('relative1.zip', entries) assert Dir.exist?('tmp') From a3245ac241e9a33bacdffbe1e45f79344b46f23e Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 15 Sep 2019 16:44:51 +0100 Subject: [PATCH 087/469] Add Rubocop tasks to the Rakefile. --- Rakefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Rakefile b/Rakefile index 44a9b287..717c6b73 100644 --- a/Rakefile +++ b/Rakefile @@ -1,5 +1,6 @@ require 'bundler/gem_tasks' require 'rake/testtask' +require 'rubocop/rake_task' task default: :test @@ -10,6 +11,8 @@ Rake::TestTask.new(:test) do |test| test.verbose = true end +RuboCop::RakeTask.new + # Rake::TestTask.new(:zip64_full_test) do |test| # test.libs << File.join(File.dirname(__FILE__), 'lib') # test.libs << File.join(File.dirname(__FILE__), 'test') From aa400355252ceb23d11ad24589184b6fa60d9d24 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 8 Feb 2020 15:12:43 +0000 Subject: [PATCH 088/469] Fix Gemspec/OrderedDependencies cop. --- .rubocop_todo.yml | 8 -------- rubyzip.gemspec | 6 +++--- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index daa76c02..b4d47815 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,14 +6,6 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 3 -# Cop supports --auto-correct. -# Configuration parameters: TreatCommentsAsGroupSeparators, Include. -# Include: **/*.gemspec -Gemspec/OrderedDependencies: - Exclude: - - 'rubyzip.gemspec' - # Offense count: 76 # Cop supports --auto-correct. Layout/EmptyLineAfterGuardClause: diff --git a/rubyzip.gemspec b/rubyzip.gemspec index 30dd2998..5b08a597 100644 --- a/rubyzip.gemspec +++ b/rubyzip.gemspec @@ -23,9 +23,9 @@ Gem::Specification.new do |s| 'wiki_uri' => 'https://github.com/rubyzip/rubyzip/wiki' } s.required_ruby_version = '>= 2.4' - s.add_development_dependency 'rake', '~> 10.3' - s.add_development_dependency 'pry', '~> 0.10' - s.add_development_dependency 'minitest', '~> 5.4' s.add_development_dependency 'coveralls', '~> 0.7' + s.add_development_dependency 'minitest', '~> 5.4' + s.add_development_dependency 'pry', '~> 0.10' + s.add_development_dependency 'rake', '~> 10.3' s.add_development_dependency 'rubocop', '~> 0.79' end From 73e405acef6a00e5c927c2126e8a33ec091b73df Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 14 Sep 2019 15:56:02 +0100 Subject: [PATCH 089/469] Fix Security/Open cop errors. --- .rubocop_todo.yml | 9 --------- lib/zip/file.rb | 4 ++-- lib/zip/filesystem.rb | 6 +++--- lib/zip/input_stream.rb | 2 +- test/encryption_test.rb | 6 +++--- test/stored_support_test.rb | 8 ++++---- 6 files changed, 13 insertions(+), 22 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b4d47815..5820055d 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -177,15 +177,6 @@ Naming/RescuedExceptionsVariableName: Naming/VariableName: Enabled: false -# Offense count: 13 -Security/Open: - Exclude: - - 'lib/zip/file.rb' - - 'lib/zip/filesystem.rb' - - 'lib/zip/input_stream.rb' - - 'test/encryption_test.rb' - - 'test/stored_support_test.rb' - # Offense count: 7 # Configuration parameters: EnforcedStyle. # SupportedStyles: inline, group diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 45017822..3b8c3338 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -167,7 +167,7 @@ def open_buffer(io, options = {}) # local entry headers (which contain the same information as the # central directory). def foreach(aZipFileName, &block) - open(aZipFileName) do |zipFile| + ::Zip::File.open(aZipFileName) do |zipFile| zipFile.each(&block) end end @@ -234,7 +234,7 @@ def split(zip_file_name, segment_size = MAX_SEGMENT_SIZE, delete_zip_file = true return if zip_file_size <= segment_size segment_count = get_segment_count_for_split(zip_file_size, segment_size) # Checking for correct zip structure - open(zip_file_name) {} + ::Zip::File.open(zip_file_name) {} partial_zip_file_name = get_partial_zip_file_name(zip_file_name, partial_zip_file_name) szip_file_index = 0 ::File.open(zip_file_name, 'rb') do |zip_file| diff --git a/lib/zip/filesystem.rb b/lib/zip/filesystem.rb index 81ad1a18..158e7f6f 100644 --- a/lib/zip/filesystem.rb +++ b/lib/zip/filesystem.rb @@ -250,7 +250,7 @@ def open(fileName, openMode = 'r', permissionInt = 0o644, &block) end def new(fileName, openMode = 'r') - open(fileName, openMode) + self.open(fileName, openMode) end def size(fileName) @@ -388,7 +388,7 @@ def stat(fileName) alias lstat stat def readlines(fileName) - open(fileName) { |is| is.readlines } + self.open(fileName) { |is| is.readlines } end def read(fileName) @@ -400,7 +400,7 @@ def popen(*args, &aProc) end def foreach(fileName, aSep = $/, &aProc) - open(fileName) { |is| is.each_line(aSep, &aProc) } + self.open(fileName) { |is| is.each_line(aSep, &aProc) } end def delete(*args) diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index b4c502f5..b0b7103d 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -100,7 +100,7 @@ def open(filename_or_io, offset = 0, decrypter = nil) def open_buffer(filename_or_io, offset = 0) warn 'open_buffer is deprecated!!! Use open instead!' - open(filename_or_io, offset) + ::Zip::InputStream.open(filename_or_io, offset) end end diff --git a/test/encryption_test.rb b/test/encryption_test.rb index 46770a17..d3ed5ffb 100644 --- a/test/encryption_test.rb +++ b/test/encryption_test.rb @@ -14,14 +14,14 @@ def teardown end def test_encrypt - test_file = open(ENCRYPT_ZIP_TEST_FILE, 'rb').read + test_file = ::File.open(ENCRYPT_ZIP_TEST_FILE, 'rb').read @rand = [250, 143, 107, 13, 143, 22, 155, 75, 228, 150, 12] @output = ::Zip::DOSTime.stub(:now, ::Zip::DOSTime.new(2014, 12, 17, 15, 56, 24)) do Random.stub(:rand, ->(_range) { @rand.shift }) do Zip::OutputStream.write_buffer(::StringIO.new(''), Zip::TraditionalEncrypter.new('password')) do |zos| zos.put_next_entry('file1.txt') - zos.write open(INPUT_FILE1).read + zos.write ::File.open(INPUT_FILE1).read end.string end end @@ -36,7 +36,7 @@ def test_decrypt entry = zis.get_next_entry assert_equal 'file1.txt', entry.name assert_equal 1327, entry.size - assert_equal open(INPUT_FILE1, 'r').read, zis.read + assert_equal ::File.open(INPUT_FILE1, 'r').read, zis.read end end end diff --git a/test/stored_support_test.rb b/test/stored_support_test.rb index 8260e4a9..e0d3ae0e 100644 --- a/test/stored_support_test.rb +++ b/test/stored_support_test.rb @@ -11,11 +11,11 @@ def test_read entry = zis.get_next_entry assert_equal 'file1.txt', entry.name assert_equal 1327, entry.size - assert_equal open(INPUT_FILE1, 'r').read, zis.read + assert_equal ::File.open(INPUT_FILE1, 'r').read, zis.read entry = zis.get_next_entry assert_equal 'file2.txt', entry.name assert_equal 41234, entry.size - assert_equal open(INPUT_FILE2, 'r').read, zis.read + assert_equal ::File.open(INPUT_FILE2, 'r').read, zis.read end end @@ -24,11 +24,11 @@ def test_encrypted_read entry = zis.get_next_entry assert_equal 'file1.txt', entry.name assert_equal 1327, entry.size - assert_equal open(INPUT_FILE1, 'r').read, zis.read + assert_equal ::File.open(INPUT_FILE1, 'r').read, zis.read entry = zis.get_next_entry assert_equal 'file2.txt', entry.name assert_equal 41234, entry.size - assert_equal open(INPUT_FILE2, 'r').read, zis.read + assert_equal ::File.open(INPUT_FILE2, 'r').read, zis.read end end end From f1154c2ecab5f22a6dfd6daead062e7f2345efe0 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 9 Feb 2020 10:39:04 +0000 Subject: [PATCH 090/469] Fix Style/OrAssignment cop. --- .rubocop_todo.yml | 6 ------ test/test_helper.rb | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 5820055d..db48ac8d 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -398,12 +398,6 @@ Style/NumericPredicate: - 'test/file_split_test.rb' - 'test/test_helper.rb' -# Offense count: 1 -# Cop supports --auto-correct. -Style/OrAssignment: - Exclude: - - 'test/test_helper.rb' - # Offense count: 2 # Cop supports --auto-correct. # Configuration parameters: AllowMultipleReturnValues. diff --git a/test/test_helper.rb b/test/test_helper.rb index 224a1eb2..7a0edfc9 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -24,7 +24,7 @@ module IOizeString def read(count = nil) @tell ||= 0 - count = size unless count + count ||= size retVal = slice(@tell, count) @tell += count retVal From 3a3ac6feb7f72a835a4285825f2e01c11b5325a1 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 14 Sep 2019 16:03:43 +0100 Subject: [PATCH 091/469] Fix Style/Semicolon cop. --- .rubocop_todo.yml | 7 ------- test/file_test.rb | 10 ++++++++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index db48ac8d..698aa814 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -438,13 +438,6 @@ Style/SafeNavigation: - 'test/filesystem/file_stat_test.rb' - 'test/test_helper.rb' -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: AllowAsExpressionSeparator. -Style/Semicolon: - Exclude: - - 'test/file_test.rb' - # Offense count: 8 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. diff --git a/test/file_test.rb b/test/file_test.rb index d7f5cb8e..90841bfb 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -250,7 +250,10 @@ def test_add_existing_entry_name_replace replacedEntry = nil ::Zip::File.open(TEST_ZIP.zip_name) do |zf| replacedEntry = zf.entries.first.name - zf.add(replacedEntry, 'test/data/file2.txt') { gotCalled = true; true } + zf.add(replacedEntry, 'test/data/file2.txt') do + gotCalled = true + true + end end assert(gotCalled) ::Zip::File.open(TEST_ZIP.zip_name) do |zf| @@ -361,7 +364,10 @@ def test_rename_to_existing_entry_overwrite renamedEntryName = nil ::Zip::File.open(TEST_ZIP.zip_name) do |zf| renamedEntryName = zf.entries[0].name - zf.rename(zf.entries[0], zf.entries[1].name) { gotCalled = true; true } + zf.rename(zf.entries[0], zf.entries[1].name) do + gotCalled = true + true + end end assert(gotCalled) From e7275dad935355b1dacfcf95e18d9b1be7493ec1 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 14 Sep 2019 16:05:23 +0100 Subject: [PATCH 092/469] Fix Style/BlockDelimiters cop errors. --- .rubocop_todo.yml | 11 ----------- test/case_sensitivity_test.rb | 4 ++-- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 698aa814..27220d16 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -196,17 +196,6 @@ Style/Alias: - 'lib/zip/ioextras/abstract_input_stream.rb' - 'lib/zip/pass_thru_decompressor.rb' -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, IgnoredMethods, AllowBracesOnProceduralOneLiners. -# SupportedStyles: line_count_based, semantic, braces_for_chaining, always_braces -# ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object -# FunctionalMethods: let, let!, subject, watch -# IgnoredMethods: lambda, proc, it -Style/BlockDelimiters: - Exclude: - - 'test/case_sensitivity_test.rb' - # Offense count: 7 # Cop supports --auto-correct. # Configuration parameters: AutoCorrect, EnforcedStyle. diff --git a/test/case_sensitivity_test.rb b/test/case_sensitivity_test.rb index 4aab1667..6ed07069 100644 --- a/test/case_sensitivity_test.rb +++ b/test/case_sensitivity_test.rb @@ -22,11 +22,11 @@ def test_add_case_sensitive zfRead = ::Zip::File.new(EMPTY_FILENAME) assert_equal(SRC_FILES.size, zfRead.entries.length) - SRC_FILES.each_with_index { |a, i| + SRC_FILES.each_with_index do |a, i| assert_equal(a.last, zfRead.entries[i].name) AssertEntry.assert_contents(a.first, zfRead.get_input_stream(a.last) { |zis| zis.read }) - } + end end # Ensure that names are treated case insensitively when adding files and +case_insensitive_match = false+ From 98c6969c18b27995febc504a09403fd2b9f57fd7 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 9 Feb 2020 10:52:06 +0000 Subject: [PATCH 093/469] Fix Layout/SpaceAroundOperators cop. --- .rubocop_todo.yml | 12 ------------ lib/zip/entry.rb | 2 +- lib/zip/extra_field/zip64.rb | 2 +- lib/zip/file.rb | 2 +- lib/zip/filesystem.rb | 6 +++--- lib/zip/input_stream.rb | 2 +- 6 files changed, 7 insertions(+), 19 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 27220d16..0e012f16 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -31,18 +31,6 @@ Layout/HashAlignment: - 'lib/zip/file.rb' - 'rubyzip.gemspec' -# Offense count: 5 -# Cop supports --auto-correct. -# Configuration parameters: AllowForAlignment, EnforcedStyleForExponentOperator. -# SupportedStylesForExponentOperator: space, no_space -Layout/SpaceAroundOperators: - Exclude: - - 'lib/zip/entry.rb' - - 'lib/zip/extra_field/zip64.rb' - - 'lib/zip/file.rb' - - 'lib/zip/filesystem.rb' - - 'lib/zip/input_stream.rb' - # Offense count: 1 Lint/AmbiguousBlockAssociation: Exclude: diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index a24fb791..d936943f 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -99,7 +99,7 @@ def time=(value) @extra.create('UniversalTime') end (@extra['UniversalTime'] || @extra['NTFS']).mtime = value - @time = value + @time = value end def file_type_is?(type) diff --git a/lib/zip/extra_field/zip64.rb b/lib/zip/extra_field/zip64.rb index 962038d8..49fa0813 100644 --- a/lib/zip/extra_field/zip64.rb +++ b/lib/zip/extra_field/zip64.rb @@ -9,7 +9,7 @@ def initialize(binstr = nil) # unparsed binary; we don't actually know what this contains # without looking for FFs in the associated file header # call parse after initializing with a binary string - @content = nil + @content = nil @original_size = nil @compressed_size = nil @relative_header_offset = nil diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 3b8c3338..c609210d 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -75,7 +75,7 @@ class File < CentralDirectory # a new archive if it doesn't exist already. def initialize(path_or_io, create = false, buffer = false, options = {}) super() - options = DEFAULT_OPTIONS.merge(options) + options = DEFAULT_OPTIONS.merge(options) @name = path_or_io.respond_to?(:path) ? path_or_io.path : path_or_io @comment = '' @create = create ? true : false # allow any truthy value to mean true diff --git a/lib/zip/filesystem.rb b/lib/zip/filesystem.rb index 158e7f6f..ef9f76b9 100644 --- a/lib/zip/filesystem.rb +++ b/lib/zip/filesystem.rb @@ -35,9 +35,9 @@ module Zip module FileSystem def initialize # :nodoc: - mappedZip = ZipFileNameMapper.new(self) - @zipFsDir = ZipFsDir.new(mappedZip) - @zipFsFile = ZipFsFile.new(mappedZip) + mappedZip = ZipFileNameMapper.new(self) + @zipFsDir = ZipFsDir.new(mappedZip) + @zipFsFile = ZipFsFile.new(mappedZip) @zipFsDir.file = @zipFsFile @zipFsFile.dir = @zipFsDir end diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index b0b7103d..0bf24854 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -51,7 +51,7 @@ class InputStream # @param offset [Integer] offset in the IO/StringIO def initialize(context, offset = 0, decrypter = nil) super() - @archive_io = get_io(context, offset) + @archive_io = get_io(context, offset) @decompressor = ::Zip::NullDecompressor @decrypter = decrypter || ::Zip::NullDecrypter.new @current_entry = nil From 20743a53b22d04dbd33149329a8d8819a4097bad Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 9 Feb 2020 10:55:41 +0000 Subject: [PATCH 094/469] Fix Lint/AmbiguousBlockAssociation cop. --- .rubocop_todo.yml | 5 ----- test/filesystem/file_nonmutating_test.rb | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 0e012f16..5bdd4fcc 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -31,11 +31,6 @@ Layout/HashAlignment: - 'lib/zip/file.rb' - 'rubyzip.gemspec' -# Offense count: 1 -Lint/AmbiguousBlockAssociation: - Exclude: - - 'test/filesystem/file_nonmutating_test.rb' - # Offense count: 1 Lint/LiteralAsCondition: Exclude: diff --git a/test/filesystem/file_nonmutating_test.rb b/test/filesystem/file_nonmutating_test.rb index 62486666..e416eec1 100644 --- a/test/filesystem/file_nonmutating_test.rb +++ b/test/filesystem/file_nonmutating_test.rb @@ -439,7 +439,7 @@ def test_glob '*/foo/**/*.txt' => ['globTest/foo/bar/baz/foo.txt'] }.each do |spec, expected_results| results = zf.glob(spec) - assert results.all? { |entry| entry.is_a? ::Zip::Entry } + assert(results.all? { |entry| entry.is_a? ::Zip::Entry }) result_strings = results.map(&:to_s) missing_matches = expected_results - result_strings From b528cae0849be758ea8b45f978422b99633b5fc6 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 9 Feb 2020 11:01:57 +0000 Subject: [PATCH 095/469] Fix Lint/LiteralAsCondition cop. This fixes Style/InfiniteLoop as a side-effect. --- .rubocop_todo.yml | 11 ----------- lib/zip/ioextras/abstract_input_stream.rb | 2 +- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 5bdd4fcc..d3567a00 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -31,11 +31,6 @@ Layout/HashAlignment: - 'lib/zip/file.rb' - 'rubyzip.gemspec' -# Offense count: 1 -Lint/LiteralAsCondition: - Exclude: - - 'lib/zip/ioextras/abstract_input_stream.rb' - # Offense count: 1 Lint/RescueException: Exclude: @@ -283,12 +278,6 @@ Style/IfUnlessModifier: - 'lib/zip/pass_thru_decompressor.rb' - 'lib/zip/streamable_stream.rb' -# Offense count: 1 -# Cop supports --auto-correct. -Style/InfiniteLoop: - Exclude: - - 'lib/zip/ioextras/abstract_input_stream.rb' - # Offense count: 1 Style/MixinUsage: Exclude: diff --git a/lib/zip/ioextras/abstract_input_stream.rb b/lib/zip/ioextras/abstract_input_stream.rb index 58678a3f..60743b23 100644 --- a/lib/zip/ioextras/abstract_input_stream.rb +++ b/lib/zip/ioextras/abstract_input_stream.rb @@ -101,7 +101,7 @@ def readline(a_sep_string = $/) end def each_line(a_sep_string = $/) - yield readline(a_sep_string) while true + loop { yield readline(a_sep_string) } rescue EOFError end From cd065d01861d983a6c154559886da46fee00f17e Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 9 Feb 2020 11:08:43 +0000 Subject: [PATCH 096/469] Fix Lint/UnusedBlockArgument cop. --- .rubocop_todo.yml | 7 ------- test/file_test.rb | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index d3567a00..72946258 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -47,13 +47,6 @@ Lint/SuppressedException: - 'test/ioextras/abstract_input_stream_test.rb' - 'test/local_entry_test.rb' -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. -Lint/UnusedBlockArgument: - Exclude: - - 'test/file_test.rb' - # Offense count: 1 # Configuration parameters: ContextCreatingMethods, MethodCreatingMethods. Lint/UselessAccessModifier: diff --git a/test/file_test.rb b/test/file_test.rb index 90841bfb..c08bbdb6 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -131,7 +131,7 @@ def test_open_buffer_no_op_does_not_change_file # Note: this may change the file if it is opened with r+b instead of rb. # The 'extra fields' in this particular zip file get reordered. File.open(test_zip, 'rb') do |file| - Zip::File.open_buffer(file) do |zf| + Zip::File.open_buffer(file) do nil # do nothing end end From e361a47c8e70e3dd51a7d6e5e7b1a4f0f9f84650 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 9 Feb 2020 11:12:16 +0000 Subject: [PATCH 097/469] Configure Lint/UselessComparison cop. Allow this in entry_test.rb so we can test <=>. --- .rubocop.yml | 5 +++++ .rubocop_todo.yml | 5 ----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index cc32da4b..70495c52 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1 +1,6 @@ inherit_from: .rubocop_todo.yml + +# Allow this "useless" test, as we are testing <=> here. +Lint/UselessComparison: + Exclude: + - 'test/entry_test.rb' diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 72946258..3c403446 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -53,11 +53,6 @@ Lint/UselessAccessModifier: Exclude: - 'lib/zip/entry.rb' -# Offense count: 1 -Lint/UselessComparison: - Exclude: - - 'test/entry_test.rb' - # Offense count: 120 Metrics/AbcSize: Max: 60 From 23ba1af4fb42b91d22f2ed78f4cfe2d3321d407d Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 9 Feb 2020 11:15:17 +0000 Subject: [PATCH 098/469] Fix Lint/RescueException cop. --- .rubocop_todo.yml | 5 ----- test/output_stream_test.rb | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 3c403446..dbdbffdb 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -31,11 +31,6 @@ Layout/HashAlignment: - 'lib/zip/file.rb' - 'rubyzip.gemspec' -# Offense count: 1 -Lint/RescueException: - Exclude: - - 'test/output_stream_test.rb' - # Offense count: 12 # Configuration parameters: AllowComments. Lint/SuppressedException: diff --git a/test/output_stream_test.rb b/test/output_stream_test.rb index a7725e22..f3875266 100644 --- a/test/output_stream_test.rb +++ b/test/output_stream_test.rb @@ -57,7 +57,7 @@ def test_cannot_open_file name = TestFiles::EMPTY_TEST_DIR begin ::Zip::OutputStream.open(name) - rescue Exception + rescue SystemCallError assert($!.kind_of?(Errno::EISDIR) || # Linux $!.kind_of?(Errno::EEXIST) || # Windows/cygwin $!.kind_of?(Errno::EACCES), # Windows From 0d4942171127d70c34898ad445db81153e755969 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 9 Feb 2020 11:17:05 +0000 Subject: [PATCH 099/469] Fix Lint/UselessAccessModifier cop. --- .rubocop_todo.yml | 6 ------ lib/zip/entry.rb | 2 -- 2 files changed, 8 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index dbdbffdb..70ca6576 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -42,12 +42,6 @@ Lint/SuppressedException: - 'test/ioextras/abstract_input_stream_test.rb' - 'test/local_entry_test.rb' -# Offense count: 1 -# Configuration parameters: ContextCreatingMethods, MethodCreatingMethods. -Lint/UselessAccessModifier: - Exclude: - - 'lib/zip/entry.rb' - # Offense count: 120 Metrics/AbcSize: Max: 60 diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index d936943f..155b136e 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -226,8 +226,6 @@ def read_local_entry(io) end end - public - def unpack_local_entry(buf) @header_signature, @version, From fff2c41d68e92d6be15449f9604280d30426ce3c Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 9 Feb 2020 11:27:11 +0000 Subject: [PATCH 100/469] Configure Lint/SuppressedException cop. In the tests we can say "anything goes", but in the main body of the code we should at least comment if we're not handling an exception fully. --- .rubocop.yml | 7 +++++++ .rubocop_todo.yml | 11 ----------- lib/zip/ioextras/abstract_input_stream.rb | 1 + 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 70495c52..111c7549 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,5 +1,12 @@ inherit_from: .rubocop_todo.yml +# In some cases we just need to catch an exception, rather than +# actually handle it. Allow the tests to make use of this shortcut. +Lint/SuppressedException: + AllowComments: true + Exclude: + - 'test/**/*.rb' + # Allow this "useless" test, as we are testing <=> here. Lint/UselessComparison: Exclude: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 70ca6576..f6e1112a 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -31,17 +31,6 @@ Layout/HashAlignment: - 'lib/zip/file.rb' - 'rubyzip.gemspec' -# Offense count: 12 -# Configuration parameters: AllowComments. -Lint/SuppressedException: - Exclude: - - 'lib/zip/ioextras/abstract_input_stream.rb' - - 'test/central_directory_entry_test.rb' - - 'test/central_directory_test.rb' - - 'test/errors_test.rb' - - 'test/ioextras/abstract_input_stream_test.rb' - - 'test/local_entry_test.rb' - # Offense count: 120 Metrics/AbcSize: Max: 60 diff --git a/lib/zip/ioextras/abstract_input_stream.rb b/lib/zip/ioextras/abstract_input_stream.rb index 60743b23..d2c0db38 100644 --- a/lib/zip/ioextras/abstract_input_stream.rb +++ b/lib/zip/ioextras/abstract_input_stream.rb @@ -103,6 +103,7 @@ def readline(a_sep_string = $/) def each_line(a_sep_string = $/) loop { yield readline(a_sep_string) } rescue EOFError + # We just need to catch this; we don't need to handle it. end alias_method :each, :each_line From cfe4972e712b36d351b6fb7eb189d28e59902ef2 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 9 Feb 2020 13:13:21 +0000 Subject: [PATCH 101/469] Fix Layout/EmptyLineAfterGuardClause cop. --- .rubocop_todo.yml | 5 ----- lib/zip/central_directory.rb | 4 ++++ lib/zip/crypto/decrypted_io.rb | 1 + lib/zip/entry.rb | 14 ++++++++++++++ lib/zip/entry_set.rb | 2 ++ lib/zip/extra_field.rb | 2 ++ lib/zip/extra_field/generic.rb | 2 ++ lib/zip/extra_field/ntfs.rb | 4 ++++ lib/zip/extra_field/old_unix.rb | 2 ++ lib/zip/extra_field/universal_time.rb | 3 +++ lib/zip/extra_field/unix.rb | 2 ++ lib/zip/extra_field/zip64.rb | 2 ++ lib/zip/file.rb | 7 +++++++ lib/zip/filesystem.rb | 11 +++++++++++ lib/zip/inflater.rb | 2 ++ lib/zip/input_stream.rb | 3 +++ lib/zip/ioextras/abstract_input_stream.rb | 4 ++++ lib/zip/output_stream.rb | 6 ++++++ lib/zip/streamable_stream.rb | 1 + samples/zipfind.rb | 1 + test/file_split_test.rb | 1 + test/file_test.rb | 1 + test/gentestfiles.rb | 1 + 23 files changed, 76 insertions(+), 5 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index f6e1112a..8ecc67db 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,11 +6,6 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 76 -# Cop supports --auto-correct. -Layout/EmptyLineAfterGuardClause: - Enabled: false - # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. diff --git a/lib/zip/central_directory.rb b/lib/zip/central_directory.rb index 0b6874ef..54ada42e 100644 --- a/lib/zip/central_directory.rb +++ b/lib/zip/central_directory.rb @@ -141,6 +141,7 @@ def read_from_stream(io) #:nodoc: def get_e_o_c_d(buf) #:nodoc: sig_index = buf.rindex([END_OF_CDS].pack('V')) raise Error, 'Zip end of central directory signature not found' unless sig_index + buf = buf.slice!((sig_index + 4)..(buf.bytesize)) def buf.read(count) @@ -166,8 +167,10 @@ def start_buf(io) def get_64_e_o_c_d(buf) #:nodoc: zip_64_start = buf.rindex([ZIP64_END_OF_CDS].pack('V')) raise Error, 'Zip64 end of central directory signature not found' unless zip_64_start + zip_64_locator = buf.rindex([ZIP64_EOCD_LOCATOR].pack('V')) raise Error, 'Zip64 end of central directory signature locator not found' unless zip_64_locator + buf = buf.slice!((zip_64_start + 4)..zip_64_locator) def buf.read(count) @@ -198,6 +201,7 @@ def self.read_from_stream(io) #:nodoc: def ==(other) #:nodoc: return false unless other.kind_of?(CentralDirectory) + @entry_set.entries.sort == other.entries.sort && comment == other.comment end end diff --git a/lib/zip/crypto/decrypted_io.rb b/lib/zip/crypto/decrypted_io.rb index 1dab17c0..cddce8a8 100644 --- a/lib/zip/crypto/decrypted_io.rb +++ b/lib/zip/crypto/decrypted_io.rb @@ -12,6 +12,7 @@ def read(length = nil, outbuf = +'') while length.nil? || (buffer.bytesize < length) break if input_finished? + buffer << produce_input end diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 155b136e..bed82f85 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -48,6 +48,7 @@ def set_default_vars_values def check_name(name) return unless name.start_with?('/') + raise ::Zip::EntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /" end @@ -104,6 +105,7 @@ def time=(value) def file_type_is?(type) raise InternalError, "current filetype is unknown: #{inspect}" unless @ftype + @ftype == type end @@ -124,6 +126,7 @@ def name_is_directory? #:nodoc:all def name_safe? cleanpath = Pathname.new(@name).cleanpath return false unless cleanpath.relative? + root = ::File::SEPARATOR naive_expanded_path = ::File.join(root, cleanpath.to_s) ::File.absolute_path(cleanpath.to_s, root) == naive_expanded_path @@ -153,6 +156,7 @@ def calculate_local_header_size #:nodoc:all # that we didn't change the header size (and thus clobber file data or something) def verify_local_header_size! return if @local_header_size.nil? + new_size = calculate_local_header_size raise Error, "local header size changed (#{@local_header_size} -> #{new_size})" if @local_header_size != new_size end @@ -255,6 +259,7 @@ def read_local_entry(io) #:nodoc:all unless @header_signature == ::Zip::LOCAL_ENTRY_SIGNATURE raise ::Zip::Error, "Zip local header magic not found at location '#{local_header_offset}'" end + set_time(@last_mod_date, @last_mod_time) @name = io.read(@name_length) @@ -274,6 +279,7 @@ def read_local_entry(io) #:nodoc:all @extra = ::Zip::ExtraField.new(extra) end end + parse_zip64_extra(true) @local_header_size = calculate_local_header_size end @@ -360,16 +366,19 @@ def set_ftype_from_c_dir_entry def check_c_dir_entry_static_header_length(buf) return if buf.bytesize == ::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH + raise Error, 'Premature end of file. Not enough data for zip cdir entry header' end def check_c_dir_entry_signature return if header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE + raise Error, "Zip local header magic not found at location '#{local_header_offset}'" end def check_c_dir_entry_comment_size return if @comment && @comment.bytesize == @comment_length + raise ::Zip::Error, 'Truncated cdir zip entry header' end @@ -408,6 +417,7 @@ def file_stat(path) # :nodoc: def get_extra_attributes_from_path(path) # :nodoc: return if Zip::RUNNING_ON_WINDOWS + stat = file_stat(path) @unix_uid = stat.uid @unix_gid = stat.gid @@ -494,6 +504,7 @@ def write_c_dir_entry(io) #:nodoc:all def ==(other) return false unless other.class == self.class + # Compares contents of local entry and exposed fields keys_equal = %w[compression_method crc compressed_size size name extra filepath].all? do |k| other.__send__(k.to_sym) == __send__(k.to_sym) @@ -635,6 +646,7 @@ def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exi def create_directory(dest_path) return if ::File.directory?(dest_path) + if ::File.exist?(dest_path) if block_given? && yield(self, dest_path) ::FileUtils.rm_f dest_path @@ -659,6 +671,7 @@ def create_symlink(dest_path) # (required when file sizes exceed 2**32, but can be used for all files) def parse_zip64_extra(for_local_header) #:nodoc:all return if @extra['Zip64'].nil? + if for_local_header @size, @compressed_size = @extra['Zip64'].parse(@size, @compressed_size) else @@ -673,6 +686,7 @@ def data_descriptor_size # create a zip64 extra information field if we need one def prep_zip64_extra(for_local_header) #:nodoc:all return unless ::Zip.write_zip64_support + need_zip64 = @size >= 0xFFFFFFFF || @compressed_size >= 0xFFFFFFFF need_zip64 ||= @local_header_offset >= 0xFFFFFFFF unless for_local_header if need_zip64 diff --git a/lib/zip/entry_set.rb b/lib/zip/entry_set.rb index 3272b2a4..9c503781 100644 --- a/lib/zip/entry_set.rb +++ b/lib/zip/entry_set.rb @@ -50,6 +50,7 @@ def dup def ==(other) return false unless other.kind_of?(EntrySet) + @entry_set.values == other.entry_set.values end @@ -60,6 +61,7 @@ def parent(entry) def glob(pattern, flags = ::File::FNM_PATHNAME | ::File::FNM_DOTMATCH | ::File::FNM_EXTGLOB) entries.map do |entry| next nil unless ::File.fnmatch(pattern, entry.name.chomp('/'), flags) + yield(entry) if block_given? entry end.compact diff --git a/lib/zip/extra_field.rb b/lib/zip/extra_field.rb index 0dcf0d5e..b8bb8a5f 100644 --- a/lib/zip/extra_field.rb +++ b/lib/zip/extra_field.rb @@ -36,6 +36,7 @@ class << s def merge(binstr) return if binstr.empty? + i = 0 while i < binstr.bytesize id = binstr[i, 2] @@ -54,6 +55,7 @@ def create(name) unless (field_class = ID_MAP.values.find { |k| k.name == name }) raise Error, "Unknown extra field '#{name}'" end + self[name] = field_class.new end diff --git a/lib/zip/extra_field/generic.rb b/lib/zip/extra_field/generic.rb index d61137fe..0bb000b3 100644 --- a/lib/zip/extra_field/generic.rb +++ b/lib/zip/extra_field/generic.rb @@ -19,11 +19,13 @@ def initial_parse(binstr) warn 'WARNING: weird extra field header ID. Skip parsing it.' return false end + [binstr[2, 2].unpack('v')[0], binstr[4..-1]] end def ==(other) return false if self.class != other.class + each do |k, v| return false if v != other[k] end diff --git a/lib/zip/extra_field/ntfs.rb b/lib/zip/extra_field/ntfs.rb index 687704d8..f4f11b2d 100644 --- a/lib/zip/extra_field/ntfs.rb +++ b/lib/zip/extra_field/ntfs.rb @@ -19,6 +19,7 @@ def initialize(binstr = nil) def merge(binstr) return if binstr.empty? + size, content = initial_parse(binstr) (size && content) || return @@ -27,6 +28,7 @@ def merge(binstr) tag1 = tags[1] return unless tag1 + ntfs_mtime, ntfs_atime, ntfs_ctime = tag1.unpack('Q= 5 # how many times should we retry? + retried += 1 retry end diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index 0bf24854..8d86897c 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -73,6 +73,7 @@ def get_next_entry # Rewinds the stream to the beginning of the current entry def rewind return if @current_entry.nil? + @lineno = 0 @pos = 0 @archive_io.seek(@current_entry.local_header_offset, IO::SEEK_SET) @@ -91,6 +92,7 @@ class << self def open(filename_or_io, offset = 0, decrypter = nil) zio = new(filename_or_io, offset, decrypter) return zio unless block_given? + begin yield zio ensure @@ -123,6 +125,7 @@ def open_entry if @current_entry && @current_entry.encrypted? && @decrypter.is_a?(NullEncrypter) raise Error, 'password required to decode zip file' end + if @current_entry && @current_entry.incomplete? && @current_entry.crc == 0 \ && @current_entry.compressed_size == 0 \ && @current_entry.size == 0 && !@complete_entry diff --git a/lib/zip/ioextras/abstract_input_stream.rb b/lib/zip/ioextras/abstract_input_stream.rb index d2c0db38..e3cf9839 100644 --- a/lib/zip/ioextras/abstract_input_stream.rb +++ b/lib/zip/ioextras/abstract_input_stream.rb @@ -35,6 +35,7 @@ def read(number_of_bytes = nil, buf = '') if tbuf.nil? || tbuf.empty? return nil if number_of_bytes + return '' end @@ -69,6 +70,7 @@ def gets(a_sep_string = $/, number_of_bytes = nil) end return read(number_of_bytes) if a_sep_string.nil? + a_sep_string = "#{$/}#{$/}" if a_sep_string.empty? buffer_index = 0 @@ -76,6 +78,7 @@ def gets(a_sep_string = $/, number_of_bytes = nil) while (match_index = @output_buffer.index(a_sep_string, buffer_index)).nil? && !over_limit buffer_index = [buffer_index, @output_buffer.bytesize - a_sep_string.bytesize].max return @output_buffer.empty? ? nil : flush if input_finished? + @output_buffer << produce_input over_limit = (number_of_bytes && @output_buffer.bytesize >= number_of_bytes) end @@ -97,6 +100,7 @@ def flush def readline(a_sep_string = $/) ret_val = gets(a_sep_string) raise EOFError unless ret_val + ret_val end diff --git a/lib/zip/output_stream.rb b/lib/zip/output_stream.rb index d9bbc4df..6ab4a484 100644 --- a/lib/zip/output_stream.rb +++ b/lib/zip/output_stream.rb @@ -49,6 +49,7 @@ def initialize(file_name, stream = false, encrypter = nil) class << self def open(file_name, encrypter = nil) return new(file_name) unless block_given? + zos = new(file_name, false, encrypter) yield zos ensure @@ -66,6 +67,7 @@ def write_buffer(io = ::StringIO.new(''), encrypter = nil) # Closes the stream and writes the central directory to the zip file def close return if @closed + finalize_current_entry update_local_headers write_central_directory @@ -76,6 +78,7 @@ def close # Closes the stream and writes the central directory to the zip file def close_buffer return @output_stream if @closed + finalize_current_entry update_local_headers write_central_directory @@ -87,6 +90,7 @@ def close_buffer # +entry+ can be a ZipEntry object or a string. def put_next_entry(entry_name, comment = nil, extra = nil, compression_method = Entry::DEFLATED, level = Zip.default_compression) raise Error, 'zip stream is closed' if @closed + new_entry = if entry_name.kind_of?(Entry) entry_name else @@ -105,6 +109,7 @@ def copy_raw_entry(entry) entry = entry.dup raise Error, 'zip stream is closed' if @closed raise Error, 'entry is not a ZipEntry' unless entry.is_a?(Entry) + finalize_current_entry @entry_set << entry src_pos = entry.local_header_offset @@ -123,6 +128,7 @@ def copy_raw_entry(entry) def finalize_current_entry return unless @current_entry + finish @current_entry.compressed_size = @output_stream.tell - @current_entry.local_header_offset - @current_entry.calculate_local_header_size @current_entry.size = @compressor.size diff --git a/lib/zip/streamable_stream.rb b/lib/zip/streamable_stream.rb index 642ddae2..90a44447 100644 --- a/lib/zip/streamable_stream.rb +++ b/lib/zip/streamable_stream.rb @@ -22,6 +22,7 @@ def get_input_stream unless @temp_file.closed? raise StandardError, "cannot open entry for reading while its open for writing - #{name}" end + @temp_file.open # reopens tempfile from top @temp_file.binmode if block_given? diff --git a/samples/zipfind.rb b/samples/zipfind.rb index 400e0a69..cd76c264 100755 --- a/samples/zipfind.rb +++ b/samples/zipfind.rb @@ -13,6 +13,7 @@ def self.find(path, zipFilePattern = /\.zip$/i) Find.find(path) do |fileName| yield(fileName) next unless zipFilePattern.match(fileName) && File.file?(fileName) + begin Zip::File.foreach(fileName) do |zipEntry| yield(fileName + File::SEPARATOR + zipEntry.to_s) diff --git a/test/file_split_test.rb b/test/file_split_test.rb index dfea837d..c488f180 100644 --- a/test/file_split_test.rb +++ b/test/file_split_test.rb @@ -28,6 +28,7 @@ def test_split result = ::Zip::File.split(TEST_ZIP.zip_name, 65_536, false) return if result.nil? + Dir["#{TEST_ZIP.zip_name}.*"].sort.each_with_index do |zip_file_name, index| File.open(zip_file_name, 'rb') do |zip_file| zip_file.read([::Zip::File::SPLIT_SIGNATURE].pack('V').size) if index == 0 diff --git a/test/file_test.rb b/test/file_test.rb index c08bbdb6..fb7ec5cc 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -623,6 +623,7 @@ def test_streaming Zip::File.open_buffer(f) do |zipfile| zipfile.each do |entry| next unless entry.name =~ /README.md/ + data = zipfile.read(entry) end end diff --git a/test/gentestfiles.rb b/test/gentestfiles.rb index 3e76e7d0..bb803b99 100755 --- a/test/gentestfiles.rb +++ b/test/gentestfiles.rb @@ -52,6 +52,7 @@ def create_random_binary(filename, size) def ensure_dir(name) if File.exist?(name) return if File.stat(name).directory? + File.delete(name) end Dir.mkdir(name) From 61c83b2a1a5e3050c1452021c794ece4c9021af4 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 9 Feb 2020 13:25:04 +0000 Subject: [PATCH 102/469] Configure Layout/HashAlignment cop. --- .rubocop.yml | 4 +++ .rubocop_todo.yml | 12 -------- lib/zip/constants.rb | 36 ++++++++++++------------ test/filesystem/file_nonmutating_test.rb | 8 ++++-- test/path_traversal_test.rb | 14 ++++----- 5 files changed, 34 insertions(+), 40 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 111c7549..ccc55719 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,5 +1,9 @@ inherit_from: .rubocop_todo.yml +Layout/HashAlignment: + EnforcedHashRocketStyle: table + EnforcedColonStyle: table + # In some cases we just need to catch an exception, rather than # actually handle it. Allow the tests to make use of this shortcut. Lint/SuppressedException: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 8ecc67db..85630180 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -14,18 +14,6 @@ Layout/EmptyLinesAroundClassBody: Exclude: - 'test/extra_field_ut_test.rb' -# Offense count: 26 -# Cop supports --auto-correct. -# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. -# SupportedHashRocketStyles: key, separator, table -# SupportedColonStyles: key, separator, table -# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit -Layout/HashAlignment: - Exclude: - - 'lib/zip/constants.rb' - - 'lib/zip/file.rb' - - 'rubyzip.gemspec' - # Offense count: 120 Metrics/AbcSize: Max: 60 diff --git a/lib/zip/constants.rb b/lib/zip/constants.rb index 6feda25d..428c5126 100644 --- a/lib/zip/constants.rb +++ b/lib/zip/constants.rb @@ -87,29 +87,29 @@ module Zip COMPRESSION_METHOD_AES = 99 COMPRESSION_METHODS = { - COMPRESSION_METHOD_STORE => 'Store (no compression)', - COMPRESSION_METHOD_SHRINK => 'Shrink', - COMPRESSION_METHOD_REDUCE_1 => 'Reduce with compression factor 1', - COMPRESSION_METHOD_REDUCE_2 => 'Reduce with compression factor 2', - COMPRESSION_METHOD_REDUCE_3 => 'Reduce with compression factor 3', - COMPRESSION_METHOD_REDUCE_4 => 'Reduce with compression factor 4', - COMPRESSION_METHOD_IMPLODE => 'Implode', + COMPRESSION_METHOD_STORE => 'Store (no compression)', + COMPRESSION_METHOD_SHRINK => 'Shrink', + COMPRESSION_METHOD_REDUCE_1 => 'Reduce with compression factor 1', + COMPRESSION_METHOD_REDUCE_2 => 'Reduce with compression factor 2', + COMPRESSION_METHOD_REDUCE_3 => 'Reduce with compression factor 3', + COMPRESSION_METHOD_REDUCE_4 => 'Reduce with compression factor 4', + COMPRESSION_METHOD_IMPLODE => 'Implode', # RESERVED = 7 - COMPRESSION_METHOD_DEFLATE => 'Deflate', - COMPRESSION_METHOD_DEFLATE_64 => 'Deflate64(tm)', + COMPRESSION_METHOD_DEFLATE => 'Deflate', + COMPRESSION_METHOD_DEFLATE_64 => 'Deflate64(tm)', COMPRESSION_METHOD_PKWARE_DCLI => 'PKWARE Data Compression Library Imploding (old IBM TERSE)', # RESERVED = 11 - COMPRESSION_METHOD_BZIP2 => 'BZIP2', + COMPRESSION_METHOD_BZIP2 => 'BZIP2', # RESERVED = 13 - COMPRESSION_METHOD_LZMA => 'LZMA', + COMPRESSION_METHOD_LZMA => 'LZMA', # RESERVED = 15 - COMPRESSION_METHOD_IBM_CMPSC => 'IBM z/OS CMPSC Compression', + COMPRESSION_METHOD_IBM_CMPSC => 'IBM z/OS CMPSC Compression', # RESERVED = 17 - COMPRESSION_METHOD_IBM_TERSE => 'IBM TERSE (new)', - COMPRESSION_METHOD_IBM_LZ77 => 'IBM LZ77 z Architecture (PFS)', - COMPRESSION_METHOD_JPEG => 'JPEG variant', - COMPRESSION_METHOD_WAVPACK => 'WavPack compressed data', - COMPRESSION_METHOD_PPMD => 'PPMd version I, Rev 1', - COMPRESSION_METHOD_AES => 'AES encryption', + COMPRESSION_METHOD_IBM_TERSE => 'IBM TERSE (new)', + COMPRESSION_METHOD_IBM_LZ77 => 'IBM LZ77 z Architecture (PFS)', + COMPRESSION_METHOD_JPEG => 'JPEG variant', + COMPRESSION_METHOD_WAVPACK => 'WavPack compressed data', + COMPRESSION_METHOD_PPMD => 'PPMd version I, Rev 1', + COMPRESSION_METHOD_AES => 'AES encryption', }.freeze end diff --git a/test/filesystem/file_nonmutating_test.rb b/test/filesystem/file_nonmutating_test.rb index e416eec1..af575fac 100644 --- a/test/filesystem/file_nonmutating_test.rb +++ b/test/filesystem/file_nonmutating_test.rb @@ -434,9 +434,11 @@ def test_glob ::Zip::File.open('test/data/globTest.zip') do |zf| { 'globTest/foo.txt' => ['globTest/foo.txt'], - '*/foo.txt' => ['globTest/foo.txt'], - '**/foo.txt' => ['globTest/foo.txt', 'globTest/foo/bar/baz/foo.txt'], - '*/foo/**/*.txt' => ['globTest/foo/bar/baz/foo.txt'] + '*/foo.txt' => ['globTest/foo.txt'], + '**/foo.txt' => [ + 'globTest/foo.txt', 'globTest/foo/bar/baz/foo.txt' + ], + '*/foo/**/*.txt' => ['globTest/foo/bar/baz/foo.txt'] }.each do |spec, expected_results| results = zf.glob(spec) assert(results.all? { |entry| entry.is_a? ::Zip::Entry }) diff --git a/test/path_traversal_test.rb b/test/path_traversal_test.rb index 6950fbcb..47c7e30f 100644 --- a/test/path_traversal_test.rb +++ b/test/path_traversal_test.rb @@ -62,7 +62,7 @@ def test_leading_dot_dot def test_non_leading_dot_dot_with_existing_folder entries = { - 'tmp/' => '', + 'tmp/' => '', 'tmp/../../moo' => /WARNING: skipped \'tmp\/\.\.\/\.\.\/moo\'/ } in_tmpdir do @@ -92,7 +92,7 @@ def test_file_symlink def test_directory_symlink # Can't create tmp/moo, because the tmp symlink is skipped. entries = { - 'tmp' => /WARNING: skipped symlink \'tmp\'/, + 'tmp' => /WARNING: skipped symlink \'tmp\'/, 'tmp/moo' => :error } in_tmpdir do @@ -104,8 +104,8 @@ def test_directory_symlink def test_two_directory_symlinks_a # Can't create par/moo because the symlinks are skipped. entries = { - 'cur' => /WARNING: skipped symlink \'cur\'/, - 'par' => /WARNING: skipped symlink \'par\'/, + 'cur' => /WARNING: skipped symlink \'cur\'/, + 'par' => /WARNING: skipped symlink \'par\'/, 'par/moo' => :error } in_tmpdir do @@ -119,7 +119,7 @@ def test_two_directory_symlinks_a def test_two_directory_symlinks_b # Can't create par/moo, because the symlinks are skipped. entries = { - 'cur' => /WARNING: skipped symlink \'cur\'/, + 'cur' => /WARNING: skipped symlink \'cur\'/, 'cur/par' => /WARNING: skipped symlink \'cur\/par\'/, 'par/moo' => :error } @@ -132,7 +132,7 @@ def test_two_directory_symlinks_b def test_entry_name_with_absolute_path_does_not_extract entries = { - '/tmp/' => /WARNING: skipped \'\/tmp\/\'/, + '/tmp/' => /WARNING: skipped \'\/tmp\/\'/, '/tmp/file.txt' => /WARNING: skipped \'\/tmp\/file.txt\'/ } in_tmpdir do @@ -156,7 +156,7 @@ def test_entry_name_with_absolute_path_extract_when_given_different_path def test_entry_name_with_relative_symlink # Doesn't create the symlink path, so can't create path/file.txt. entries = { - 'path' => /WARNING: skipped symlink \'path\'/, + 'path' => /WARNING: skipped symlink \'path\'/, 'path/file.txt' => :error } in_tmpdir do From 68259ed7b01ea50ab24a427cd42bca01ebd33077 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 9 Feb 2020 14:57:28 +0000 Subject: [PATCH 103/469] Fix Style/Encoding cop. --- .rubocop_todo.yml | 8 -------- rubyzip.gemspec | 2 -- test/errors_test.rb | 2 -- test/unicode_file_names_and_comments_test.rb | 2 -- 4 files changed, 14 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 85630180..c6c22138 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -161,14 +161,6 @@ Style/CommentedKeyword: Style/Documentation: Enabled: false -# Offense count: 3 -# Cop supports --auto-correct. -Style/Encoding: - Exclude: - - 'rubyzip.gemspec' - - 'test/errors_test.rb' - - 'test/unicode_file_names_and_comments_test.rb' - # Offense count: 2 Style/EvalWithLocation: Exclude: diff --git a/rubyzip.gemspec b/rubyzip.gemspec index 5b08a597..59c04c91 100644 --- a/rubyzip.gemspec +++ b/rubyzip.gemspec @@ -1,5 +1,3 @@ -#-*- encoding: utf-8 -*- - lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'zip/version' diff --git a/test/errors_test.rb b/test/errors_test.rb index 2c6adb2f..5e6260f8 100644 --- a/test/errors_test.rb +++ b/test/errors_test.rb @@ -1,5 +1,3 @@ -# encoding: utf-8 - require 'test_helper' class ErrorsTest < MiniTest::Test diff --git a/test/unicode_file_names_and_comments_test.rb b/test/unicode_file_names_and_comments_test.rb index aac3e256..4d2fc20f 100644 --- a/test/unicode_file_names_and_comments_test.rb +++ b/test/unicode_file_names_and_comments_test.rb @@ -1,5 +1,3 @@ -# encoding: utf-8 - require 'test_helper' class ZipUnicodeFileNamesAndComments < MiniTest::Test From 70d036b3ad32e533b21881c23bd06e1ede8eeb0f Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 15 Sep 2019 17:06:27 +0100 Subject: [PATCH 104/469] Fix Style/ExpandPathArguments cop. --- .rubocop_todo.yml | 6 ------ rubyzip.gemspec | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index c6c22138..9b89750c 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -166,12 +166,6 @@ Style/EvalWithLocation: Exclude: - 'test/test_helper.rb' -# Offense count: 1 -# Cop supports --auto-correct. -Style/ExpandPathArguments: - Exclude: - - 'rubyzip.gemspec' - # Offense count: 1 # Configuration parameters: EnforcedStyle. # SupportedStyles: left_coerce, right_coerce, single_coerce, fdiv diff --git a/rubyzip.gemspec b/rubyzip.gemspec index 59c04c91..1707ef5d 100644 --- a/rubyzip.gemspec +++ b/rubyzip.gemspec @@ -1,4 +1,4 @@ -lib = File.expand_path('../lib', __FILE__) +lib = File.expand_path('lib', __dir__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'zip/version' From 6544563dd0828f884ecc177265ea0b06af42d38f Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 9 Feb 2020 15:08:24 +0000 Subject: [PATCH 105/469] Configure Style/ZeroLengthPredicate so it doesn't misfire. --- .rubocop.yml | 7 +++++++ .rubocop_todo.yml | 7 ------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index ccc55719..36e8cf2e 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -15,3 +15,10 @@ Lint/SuppressedException: Lint/UselessComparison: Exclude: - 'test/entry_test.rb' + +# Turn this cop off for these files as it fires for objects without +# an empty? method. +Style/ZeroLengthPredicate: + Exclude: + - 'lib/zip/file.rb' + - 'lib/zip/input_stream.rb' diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 9b89750c..fe523ebb 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -437,13 +437,6 @@ Style/UnpackFirst: - 'lib/zip/extra_field/generic.rb' - 'lib/zip/extra_field/zip64.rb' -# Offense count: 3 -# Cop supports --auto-correct. -Style/ZeroLengthPredicate: - Exclude: - - 'lib/zip/file.rb' - - 'lib/zip/input_stream.rb' - # Offense count: 247 # Cop supports --auto-correct. # Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. From 19aa7e834c5613d1565e67784b902ba96c4b60e2 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 15 Sep 2019 17:36:50 +0100 Subject: [PATCH 106/469] Fix Style/RescueStandardError cop. --- .rubocop_todo.yml | 8 -------- test/gentestfiles.rb | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index fe523ebb..aef8638b 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -312,14 +312,6 @@ Style/RegexpLiteral: - 'test/path_traversal_test.rb' - 'test/settings_test.rb' -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: implicit, explicit -Style/RescueStandardError: - Exclude: - - 'test/gentestfiles.rb' - # Offense count: 17 # Cop supports --auto-correct. # Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods. diff --git a/test/gentestfiles.rb b/test/gentestfiles.rb index bb803b99..a50f51d5 100755 --- a/test/gentestfiles.rb +++ b/test/gentestfiles.rb @@ -107,7 +107,7 @@ def self.create_test_zips raise "failed to create test zip '#{TEST_ZIP3.zip_name}'" unless system("/usr/bin/zip -q #{TEST_ZIP3.zip_name} #{TEST_ZIP3.entry_names.join(' ')}") raise "failed to create test zip '#{TEST_ZIP4.zip_name}'" unless system("/usr/bin/zip -q #{TEST_ZIP4.zip_name} #{TEST_ZIP4.entry_names.join(' ')}") - rescue + rescue StandardError # If there are any Windows developers wanting to use a command line zip.exe # to help create the following files, there's a free one available from # http://stahlworks.com/dev/index.php?tool=zipunzip From 5a1baf46ab41299932a1eb8d50c96ffa9aff06ad Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 15 Sep 2019 17:40:12 +0100 Subject: [PATCH 107/469] Fix Style/RedundantReturn cop. --- .rubocop_todo.yml | 7 ------- lib/zip/central_directory.rb | 4 ++-- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index aef8638b..a78a480d 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -294,13 +294,6 @@ Style/NumericPredicate: - 'test/file_split_test.rb' - 'test/test_helper.rb' -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: AllowMultipleReturnValues. -Style/RedundantReturn: - Exclude: - - 'lib/zip/central_directory.rb' - # Offense count: 12 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, AllowInnerSlashes. diff --git a/lib/zip/central_directory.rb b/lib/zip/central_directory.rb index 54ada42e..04e0b1e6 100644 --- a/lib/zip/central_directory.rb +++ b/lib/zip/central_directory.rb @@ -194,9 +194,9 @@ def size def self.read_from_stream(io) #:nodoc: cdir = new cdir.read_from_stream(io) - return cdir + cdir rescue Error - return nil + nil end def ==(other) #:nodoc: From b3c4c37882e761ee8976045484286fcf5eba1e30 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 15 Sep 2019 19:01:57 +0100 Subject: [PATCH 108/469] Fix Style/NonNilCheck cop. Use the `refute_nil` method for most of these. --- .rubocop_todo.yml | 9 --------- test/case_sensitivity_test.rb | 2 +- test/file_test.rb | 16 ++++++++-------- test/settings_test.rb | 4 ++-- 4 files changed, 11 insertions(+), 20 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index a78a480d..f04bf510 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -252,15 +252,6 @@ Style/Next: Exclude: - 'lib/zip/entry.rb' -# Offense count: 7 -# Cop supports --auto-correct. -# Configuration parameters: IncludeSemanticChanges. -Style/NonNilCheck: - Exclude: - - 'test/case_sensitivity_test.rb' - - 'test/file_test.rb' - - 'test/settings_test.rb' - # Offense count: 2 # Cop supports --auto-correct. # Configuration parameters: EnforcedOctalStyle. diff --git a/test/case_sensitivity_test.rb b/test/case_sensitivity_test.rb index 6ed07069..7749e06b 100644 --- a/test/case_sensitivity_test.rb +++ b/test/case_sensitivity_test.rb @@ -63,7 +63,7 @@ def test_add_case_sensitive_read_case_insensitive private def assert_contains(zf, entryName, filename = entryName) - assert(zf.entries.detect { |e| e.name == entryName } != nil, "entry #{entryName} not in #{zf.entries.join(', ')} in zip file #{zf}") + refute_nil(zf.entries.detect { |e| e.name == entryName }, "entry #{entryName} not in #{zf.entries.join(', ')} in zip file #{zf}") assert_entry_contents(zf, entryName, filename) if File.exist?(filename) end end diff --git a/test/file_test.rb b/test/file_test.rb index fb7ec5cc..392a5027 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -432,8 +432,8 @@ def test_commit zf.commit zfRead = ::Zip::File.new(TEST_ZIP.zip_name) - assert(zfRead.entries.detect { |e| e.name == newName } != nil) - assert(zfRead.entries.detect { |e| e.name == oldName }.nil?) + refute_nil(zfRead.entries.detect { |e| e.name == newName }) + assert_nil(zfRead.entries.detect { |e| e.name == oldName }) zfRead.close zf.close @@ -451,8 +451,8 @@ def test_double_commit(filename = 'test/data/generated/double_commit_test.zip') zf.commit zf.close zf2 = ::Zip::File.open(filename) - assert(zf2.entries.detect { |e| e.name == 'test1.txt' } != nil) - assert(zf2.entries.detect { |e| e.name == 'test2.txt' } != nil) + refute_nil(zf2.entries.detect { |e| e.name == 'test1.txt' }) + refute_nil(zf2.entries.detect { |e| e.name == 'test2.txt' }) res = system("unzip -tqq #{filename}") assert_equal(res, true) end @@ -471,8 +471,8 @@ def test_write_buffer buffer = zf.write_buffer(io) File.open(TEST_ZIP.zip_name, 'wb') { |f| f.write buffer.string } zfRead = ::Zip::File.new(TEST_ZIP.zip_name) - assert(zfRead.entries.detect { |e| e.name == newName } != nil) - assert(zfRead.entries.detect { |e| e.name == oldName }.nil?) + refute_nil(zfRead.entries.detect { |e| e.name == newName }) + assert_nil(zfRead.entries.detect { |e| e.name == oldName }) zfRead.close zf.close @@ -678,11 +678,11 @@ def test_find_get_entry private def assert_contains(zf, entryName, filename = entryName) - assert(zf.entries.detect { |e| e.name == entryName } != nil, "entry #{entryName} not in #{zf.entries.join(', ')} in zip file #{zf}") + refute_nil(zf.entries.detect { |e| e.name == entryName }, "entry #{entryName} not in #{zf.entries.join(', ')} in zip file #{zf}") assert_entry_contents(zf, entryName, filename) if File.exist?(filename) end def assert_not_contains(zf, entryName) - assert(zf.entries.detect { |e| e.name == entryName }.nil?, "entry #{entryName} in #{zf.entries.join(', ')} in zip file #{zf}") + assert_nil(zf.entries.detect { |e| e.name == entryName }, "entry #{entryName} in #{zf.entries.join(', ')} in zip file #{zf}") end end diff --git a/test/settings_test.rb b/test/settings_test.rb index 7c1331a6..ab5aa223 100644 --- a/test/settings_test.rb +++ b/test/settings_test.rb @@ -18,7 +18,7 @@ def teardown end def open_zip(&aProc) - assert(!aProc.nil?) + refute_nil(aProc) ::Zip::File.open(TestZipFile::TEST_ZIP4.zip_name, &aProc) end @@ -89,7 +89,7 @@ def test_true_warn_invalid_date private def assert_contains(zf, entryName, filename = entryName) - assert(zf.entries.detect { |e| e.name == entryName } != nil, "entry #{entryName} not in #{zf.entries.join(', ')} in zip file #{zf}") + refute_nil(zf.entries.detect { |e| e.name == entryName }, "entry #{entryName} not in #{zf.entries.join(', ')} in zip file #{zf}") assert_entry_contents(zf, entryName, filename) if File.exist?(filename) end end From b3f241353a43f82453e7f6789e3c5551e001ff8f Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 9 Feb 2020 15:23:35 +0000 Subject: [PATCH 109/469] Fix Style/CommentedKeyword cop. --- .rubocop_todo.yml | 6 ------ lib/zip/ioextras.rb | 2 +- lib/zip/streamable_stream.rb | 2 +- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index f04bf510..cb777371 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -151,12 +151,6 @@ Style/ClassCheck: - 'test/ioextras/fake_io_test.rb' - 'test/output_stream_test.rb' -# Offense count: 2 -Style/CommentedKeyword: - Exclude: - - 'lib/zip/ioextras.rb' - - 'lib/zip/streamable_stream.rb' - # Offense count: 26 Style/Documentation: Enabled: false diff --git a/lib/zip/ioextras.rb b/lib/zip/ioextras.rb index 2412480b..63774d33 100644 --- a/lib/zip/ioextras.rb +++ b/lib/zip/ioextras.rb @@ -25,7 +25,7 @@ def kind_of?(object) object == IO || super end end - end # IOExtras namespace module + end end require 'zip/ioextras/abstract_input_stream' diff --git a/lib/zip/streamable_stream.rb b/lib/zip/streamable_stream.rb index 90a44447..1a726b99 100644 --- a/lib/zip/streamable_stream.rb +++ b/lib/zip/streamable_stream.rb @@ -1,5 +1,5 @@ module Zip - class StreamableStream < DelegateClass(Entry) # nodoc:all + class StreamableStream < DelegateClass(Entry) # :nodoc:all def initialize(entry) super(entry) @temp_file = Tempfile.new(::File.basename(name)) From 468a80ce0224ff56ab27eb34c5664d2d59e098c3 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 21 Sep 2019 19:11:37 +0100 Subject: [PATCH 110/469] Fix Style/IfInsideElse cop. --- .rubocop_todo.yml | 6 ------ lib/zip/entry.rb | 10 +++++----- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index cb777371..03f18960 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -189,12 +189,6 @@ Style/GuardClause: - 'samples/zipfind.rb' - 'test/test_helper.rb' -# Offense count: 1 -# Configuration parameters: AllowIfModifier. -Style/IfInsideElse: - Exclude: - - 'lib/zip/entry.rb' - # Offense count: 17 # Cop supports --auto-correct. Style/IfUnlessModifier: diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index bed82f85..d3470e19 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -272,12 +272,12 @@ def read_local_entry(io) #:nodoc:all if extra && extra.bytesize != @extra_length raise ::Zip::Error, 'Truncated local zip entry header' + end + + if @extra.is_a?(::Zip::ExtraField) + @extra.merge(extra) if extra else - if @extra.is_a?(::Zip::ExtraField) - @extra.merge(extra) if extra - else - @extra = ::Zip::ExtraField.new(extra) - end + @extra = ::Zip::ExtraField.new(extra) end parse_zip64_extra(true) From 3121ad066f1eacc008eb3a2468648ea871cd1ac7 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 9 Feb 2020 15:28:30 +0000 Subject: [PATCH 111/469] Fix Style/Alias cop. --- .rubocop_todo.yml | 10 ---------- lib/zip/inflater.rb | 2 +- lib/zip/ioextras/abstract_input_stream.rb | 4 ++-- lib/zip/pass_thru_decompressor.rb | 2 +- 4 files changed, 4 insertions(+), 14 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 03f18960..896a891e 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -113,16 +113,6 @@ Style/AccessModifierDeclarations: - 'lib/zip/extra_field/zip64.rb' - 'lib/zip/filesystem.rb' -# Offense count: 4 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: prefer_alias, prefer_alias_method -Style/Alias: - Exclude: - - 'lib/zip/inflater.rb' - - 'lib/zip/ioextras/abstract_input_stream.rb' - - 'lib/zip/pass_thru_decompressor.rb' - # Offense count: 7 # Cop supports --auto-correct. # Configuration parameters: AutoCorrect, EnforcedStyle. diff --git a/lib/zip/inflater.rb b/lib/zip/inflater.rb index ff12c10e..f63de688 100644 --- a/lib/zip/inflater.rb +++ b/lib/zip/inflater.rb @@ -23,7 +23,7 @@ def eof @buffer.empty? && input_finished? end - alias_method :eof?, :eof + alias eof? eof private diff --git a/lib/zip/ioextras/abstract_input_stream.rb b/lib/zip/ioextras/abstract_input_stream.rb index e3cf9839..85f4974e 100644 --- a/lib/zip/ioextras/abstract_input_stream.rb +++ b/lib/zip/ioextras/abstract_input_stream.rb @@ -110,13 +110,13 @@ def each_line(a_sep_string = $/) # We just need to catch this; we don't need to handle it. end - alias_method :each, :each_line + alias each each_line def eof @output_buffer.empty? && input_finished? end - alias_method :eof?, :eof + alias eof? eof end end end diff --git a/lib/zip/pass_thru_decompressor.rb b/lib/zip/pass_thru_decompressor.rb index ac21b61e..730882d3 100644 --- a/lib/zip/pass_thru_decompressor.rb +++ b/lib/zip/pass_thru_decompressor.rb @@ -20,7 +20,7 @@ def eof @read_so_far >= decompressed_size end - alias_method :eof?, :eof + alias eof? eof end ::Zip::Decompressor.register(::Zip::COMPRESSION_METHOD_STORE, ::Zip::PassThruDecompressor) From 1b8f1a6f3ca7d9542e3fdb230762cd3af20534e1 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 21 Sep 2019 19:24:52 +0100 Subject: [PATCH 112/469] Fix Naming/RescuedExceptionsVariableName cop. --- .rubocop_todo.yml | 7 ------- samples/zipfind.rb | 4 ++-- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 896a891e..6a4e86f3 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -91,13 +91,6 @@ Naming/MemoizedInstanceVariableName: Naming/MethodParameterName: Enabled: false -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: PreferredName. -Naming/RescuedExceptionsVariableName: - Exclude: - - 'samples/zipfind.rb' - # Offense count: 721 # Configuration parameters: EnforcedStyle. # SupportedStyles: snake_case, camelCase diff --git a/samples/zipfind.rb b/samples/zipfind.rb index cd76c264..7a8ac452 100755 --- a/samples/zipfind.rb +++ b/samples/zipfind.rb @@ -18,8 +18,8 @@ def self.find(path, zipFilePattern = /\.zip$/i) Zip::File.foreach(fileName) do |zipEntry| yield(fileName + File::SEPARATOR + zipEntry.to_s) end - rescue Errno::EACCES => ex - puts ex + rescue Errno::EACCES => e + puts e end end end From bce841639e9d02cb48edff375dc93d883b3ff134 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 21 Sep 2019 19:34:41 +0100 Subject: [PATCH 113/469] Fix Style/MixinUsage cop. --- .rubocop_todo.yml | 5 ----- samples/write_simple.rb | 4 +--- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 6a4e86f3..21879bf2 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -184,11 +184,6 @@ Style/IfUnlessModifier: - 'lib/zip/pass_thru_decompressor.rb' - 'lib/zip/streamable_stream.rb' -# Offense count: 1 -Style/MixinUsage: - Exclude: - - 'samples/write_simple.rb' - # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, Autocorrect. diff --git a/samples/write_simple.rb b/samples/write_simple.rb index be2a9704..0c534ede 100755 --- a/samples/write_simple.rb +++ b/samples/write_simple.rb @@ -4,9 +4,7 @@ require 'zip' -include Zip - -OutputStream.open('simple.zip') do |zos| +::Zip::OutputStream.open('simple.zip') do |zos| zos.put_next_entry 'entry.txt' zos.puts 'Hello world' end From 45f4c2dc29a388ef4510eb186f0240d91d8d4f95 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 9 Feb 2020 15:50:00 +0000 Subject: [PATCH 114/469] Fix Style/GuardClause cop. --- .rubocop_todo.yml | 9 --------- lib/zip/entry.rb | 17 ++++++----------- lib/zip/extra_field/generic.rb | 13 ++++++------- samples/zipfind.rb | 8 ++++---- test/test_helper.rb | 25 ++++++++++++------------- 5 files changed, 28 insertions(+), 44 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 21879bf2..b35b103e 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -163,15 +163,6 @@ Style/FormatStringToken: Style/FrozenStringLiteralComment: Enabled: false -# Offense count: 8 -# Configuration parameters: MinBodyLength. -Style/GuardClause: - Exclude: - - 'lib/zip/entry.rb' - - 'lib/zip/extra_field/generic.rb' - - 'samples/zipfind.rb' - - 'test/test_helper.rb' - # Offense count: 17 # Cop supports --auto-correct. Style/IfUnlessModifier: diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index d3470e19..0801ad26 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -182,12 +182,9 @@ def extract(dest_path = nil, &block) dest_path ||= @name block ||= proc { ::Zip.on_exists_proc } - if directory? || file? || symlink? - __send__("create_#{@ftype}", dest_path, &block) - else - raise "unknown file type #{inspect}" - end + raise "unknown file type #{inspect}" unless directory? || file? || symlink? + __send__("create_#{@ftype}", dest_path, &block) self end @@ -630,12 +627,10 @@ def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exi bytes_written += buf.bytesize if bytes_written > size && !warned message = "entry '#{name}' should be #{size}B, but is larger when inflated." - if ::Zip.validate_entry_sizes - raise ::Zip::EntrySizeError, message - else - warn "WARNING: #{message}" - warned = true - end + raise ::Zip::EntrySizeError, message if ::Zip.validate_entry_sizes + + warn "WARNING: #{message}" + warned = true end end end diff --git a/lib/zip/extra_field/generic.rb b/lib/zip/extra_field/generic.rb index 0bb000b3..7baf9e4a 100644 --- a/lib/zip/extra_field/generic.rb +++ b/lib/zip/extra_field/generic.rb @@ -1,9 +1,9 @@ module Zip class ExtraField::Generic def self.register_map - if const_defined?(:HEADER_ID) - ::Zip::ExtraField::ID_MAP[const_get(:HEADER_ID)] = self - end + return unless const_defined?(:HEADER_ID) + + ::Zip::ExtraField::ID_MAP[const_get(:HEADER_ID)] = self end def self.name @@ -12,10 +12,9 @@ def self.name # return field [size, content] or false def initial_parse(binstr) - if !binstr - # If nil, start with empty. - return false - elsif binstr[0, 2] != self.class.const_get(:HEADER_ID) + return false unless binstr + + if binstr[0, 2] != self.class.const_get(:HEADER_ID) warn 'WARNING: weird extra field header ID. Skip parsing it.' return false end diff --git a/samples/zipfind.rb b/samples/zipfind.rb index 7a8ac452..59ca1fdb 100755 --- a/samples/zipfind.rb +++ b/samples/zipfind.rb @@ -48,10 +48,10 @@ def self.run(args) end def self.check_args(args) - if args.size != 3 - usage - exit - end + return if args.size == 3 + + usage + exit end def self.usage diff --git a/test/test_helper.rb b/test/test_helper.rb index 7a0edfc9..d1777bd7 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -42,11 +42,10 @@ def seek(index, offset) else raise 'Error in test method IOizeString::seek' end - if newPos < 0 || newPos >= size - raise Errno::EINVAL - else - @tell = newPos - end + + raise Errno::EINVAL if newPos < 0 || newPos >= size + + @tell = newPos end def reset @@ -107,14 +106,14 @@ def assert_entry_contents_for_stream(filename, zis, entryName) def self.assert_contents(filename, aString) fileContents = '' File.open(filename, 'rb') { |f| fileContents = f.read } - if fileContents != aString - if fileContents.length > 400 || aString.length > 400 - stringFile = filename + '.other' - File.open(stringFile, 'wb') { |f| f << aString } - fail("File '#{filename}' is different from contents of string stored in '#{stringFile}'") - else - assert_equal(fileContents, aString) - end + return unless fileContents != aString + + if fileContents.length > 400 || aString.length > 400 + stringFile = filename + '.other' + File.open(stringFile, 'wb') { |f| f << aString } + fail("File '#{filename}' is different from contents of string stored in '#{stringFile}'") + else + assert_equal(fileContents, aString) end end From 2e11a88fd2f9bd6a21df95dc971be8924cdbb695 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 9 Feb 2020 15:57:46 +0000 Subject: [PATCH 115/469] Fix Style/StringLiterals cop. --- .rubocop_todo.yml | 11 ----------- lib/zip/crypto/decrypted_io.rb | 2 +- lib/zip/inflater.rb | 2 +- lib/zip/pass_thru_decompressor.rb | 2 +- test/file_test.rb | 2 +- 5 files changed, 4 insertions(+), 15 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b35b103e..88e3f86b 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -301,17 +301,6 @@ Style/SpecialGlobalVars: - 'test/output_stream_test.rb' - 'test/test_helper.rb' -# Offense count: 4 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline. -# SupportedStyles: single_quotes, double_quotes -Style/StringLiterals: - Exclude: - - 'lib/zip/crypto/decrypted_io.rb' - - 'lib/zip/inflater.rb' - - 'lib/zip/pass_thru_decompressor.rb' - - 'test/file_test.rb' - # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: MinSize. diff --git a/lib/zip/crypto/decrypted_io.rb b/lib/zip/crypto/decrypted_io.rb index cddce8a8..8d8191f0 100644 --- a/lib/zip/crypto/decrypted_io.rb +++ b/lib/zip/crypto/decrypted_io.rb @@ -8,7 +8,7 @@ def initialize(io, decrypter) end def read(length = nil, outbuf = +'') - return ((length.nil? || length.zero?) ? "" : nil) if eof + return ((length.nil? || length.zero?) ? '' : nil) if eof while length.nil? || (buffer.bytesize < length) break if input_finished? diff --git a/lib/zip/inflater.rb b/lib/zip/inflater.rb index f63de688..609ccc79 100644 --- a/lib/zip/inflater.rb +++ b/lib/zip/inflater.rb @@ -8,7 +8,7 @@ def initialize(*args) end def read(length = nil, outbuf = '') - return ((length.nil? || length.zero?) ? "" : nil) if eof + return ((length.nil? || length.zero?) ? '' : nil) if eof while length.nil? || (@buffer.bytesize < length) break if input_finished? diff --git a/lib/zip/pass_thru_decompressor.rb b/lib/zip/pass_thru_decompressor.rb index 730882d3..d7892ce4 100644 --- a/lib/zip/pass_thru_decompressor.rb +++ b/lib/zip/pass_thru_decompressor.rb @@ -6,7 +6,7 @@ def initialize(*args) end def read(length = nil, outbuf = '') - return ((length.nil? || length.zero?) ? "" : nil) if eof + return ((length.nil? || length.zero?) ? '' : nil) if eof if length.nil? || (@read_so_far + length) > decompressed_size length = decompressed_size - @read_so_far diff --git a/test/file_test.rb b/test/file_test.rb index 392a5027..ba3f0eac 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -57,7 +57,7 @@ def test_create_from_scratch_with_old_create_parameter def test_get_input_stream_stored_with_gpflag_bit3 ::Zip::File.open('test/data/gpbit3stored.zip') do |zf| - assert_equal("foo\n", zf.read("foo.txt")) + assert_equal("foo\n", zf.read('foo.txt')) end end From cc0e3723519598c14253078d69cdbe96eb2d8551 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 9 Feb 2020 16:00:05 +0000 Subject: [PATCH 116/469] Configure Style/SymbolArray cop. --- .rubocop.yml | 3 +++ .rubocop_todo.yml | 7 ------- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 36e8cf2e..63b8ad8c 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -16,6 +16,9 @@ Lint/UselessComparison: Exclude: - 'test/entry_test.rb' +Style/SymbolArray: + EnforcedStyle: brackets + # Turn this cop off for these files as it fires for objects without # an empty? method. Style/ZeroLengthPredicate: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 88e3f86b..362ab5c2 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -301,13 +301,6 @@ Style/SpecialGlobalVars: - 'test/output_stream_test.rb' - 'test/test_helper.rb' -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: MinSize. -# SupportedStyles: percent, brackets -Style/SymbolArray: - EnforcedStyle: brackets - # Offense count: 42 # Cop supports --auto-correct. # Configuration parameters: IgnoredMethods. From 4e1b679c73f5f01e278e8808344ab9d187e9e2a3 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 22 Sep 2019 10:06:20 +0100 Subject: [PATCH 117/469] Fix Style/TrailingCommaInArrayLiteral cop. --- .rubocop_todo.yml | 8 -------- lib/zip/central_directory.rb | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 362ab5c2..ad2bed27 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -327,14 +327,6 @@ Style/TernaryParentheses: - 'lib/zip/inflater.rb' - 'lib/zip/pass_thru_decompressor.rb' -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyleForMultiline. -# SupportedStylesForMultiline: comma, consistent_comma, no_comma -Style/TrailingCommaInArrayLiteral: - Exclude: - - 'lib/zip/central_directory.rb' - # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyleForMultiline. diff --git a/lib/zip/central_directory.rb b/lib/zip/central_directory.rb index 04e0b1e6..496d668d 100644 --- a/lib/zip/central_directory.rb +++ b/lib/zip/central_directory.rb @@ -65,7 +65,7 @@ def write_64_e_o_c_d(io, offset, cdir_size) #:nodoc: @entry_set ? @entry_set.size : 0, # number of entries on this disk @entry_set ? @entry_set.size : 0, # number of entries total cdir_size, # size of central directory - offset, # offset of start of central directory in its disk + offset # offset of start of central directory in its disk ] io << tmp.pack('VQ Date: Sun, 22 Sep 2019 11:23:35 +0100 Subject: [PATCH 118/469] Fix Style/UnpackFirst cop. --- .rubocop_todo.yml | 9 --------- lib/zip/entry.rb | 6 +++--- lib/zip/extra_field.rb | 2 +- lib/zip/extra_field/generic.rb | 2 +- lib/zip/extra_field/zip64.rb | 2 +- 5 files changed, 6 insertions(+), 15 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index ad2bed27..0946af8d 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -335,15 +335,6 @@ Style/TrailingCommaInHashLiteral: Exclude: - 'lib/zip/constants.rb' -# Offense count: 6 -# Cop supports --auto-correct. -Style/UnpackFirst: - Exclude: - - 'lib/zip/entry.rb' - - 'lib/zip/extra_field.rb' - - 'lib/zip/extra_field/generic.rb' - - 'lib/zip/extra_field/zip64.rb' - # Offense count: 247 # Cop supports --auto-correct. # Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 0801ad26..44b3319d 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -194,15 +194,15 @@ def to_s class << self def read_zip_short(io) # :nodoc: - io.read(2).unpack('v')[0] + io.read(2).unpack1('v') end def read_zip_long(io) # :nodoc: - io.read(4).unpack('V')[0] + io.read(4).unpack1('V') end def read_zip_64_long(io) # :nodoc: - io.read(8).unpack('Q<')[0] + io.read(8).unpack1('Q<') end def read_c_dir_entry(io) #:nodoc:all diff --git a/lib/zip/extra_field.rb b/lib/zip/extra_field.rb index b8bb8a5f..e4b00b66 100644 --- a/lib/zip/extra_field.rb +++ b/lib/zip/extra_field.rb @@ -40,7 +40,7 @@ def merge(binstr) i = 0 while i < binstr.bytesize id = binstr[i, 2] - len = binstr[i + 2, 2].to_s.unpack('v').first + len = binstr[i + 2, 2].to_s.unpack1('v') if id && ID_MAP.member?(id) extra_field_type_exist(binstr, id, len, i) elsif id diff --git a/lib/zip/extra_field/generic.rb b/lib/zip/extra_field/generic.rb index 7baf9e4a..5eb702d6 100644 --- a/lib/zip/extra_field/generic.rb +++ b/lib/zip/extra_field/generic.rb @@ -19,7 +19,7 @@ def initial_parse(binstr) return false end - [binstr[2, 2].unpack('v')[0], binstr[4..-1]] + [binstr[2, 2].unpack1('v'), binstr[4..-1]] end def ==(other) diff --git a/lib/zip/extra_field/zip64.rb b/lib/zip/extra_field/zip64.rb index 7e2954d0..9826c6cf 100644 --- a/lib/zip/extra_field/zip64.rb +++ b/lib/zip/extra_field/zip64.rb @@ -46,7 +46,7 @@ def parse(original_size, compressed_size, relative_header_offset = nil, disk_sta end def extract(size, format) - @content.slice!(0, size).unpack(format)[0] + @content.slice!(0, size).unpack1(format) end private :extract From 41f2359c4bb4befead13d8928c882689dc38d259 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 9 Feb 2020 16:07:46 +0000 Subject: [PATCH 119/469] Configure Style/RegexpLiteral cop. Allow inner slashes when using // for regex literals. Allow the Guardfile to use a syntax that is more consistent with its own style. --- .rubocop.yml | 7 +++++++ .rubocop_todo.yml | 11 ----------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 63b8ad8c..03345f89 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -16,6 +16,13 @@ Lint/UselessComparison: Exclude: - 'test/entry_test.rb' +# Allow inner slashes when using // for regex literals. Allow the +# Guardfile to use a syntax that is more consistent with its own style. +Style/RegexpLiteral: + AllowInnerSlashes: true + Exclude: + - 'Guardfile' + Style/SymbolArray: EnforcedStyle: brackets diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 0946af8d..a0668e19 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -242,17 +242,6 @@ Style/NumericPredicate: - 'test/file_split_test.rb' - 'test/test_helper.rb' -# Offense count: 12 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, AllowInnerSlashes. -# SupportedStyles: slashes, percent_r, mixed -Style/RegexpLiteral: - Exclude: - - 'lib/zip/filesystem.rb' - - 'test/entry_test.rb' - - 'test/path_traversal_test.rb' - - 'test/settings_test.rb' - # Offense count: 17 # Cop supports --auto-correct. # Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods. From 5e32204ec464d61adec6dc43790844d087d47f7c Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 9 Feb 2020 16:11:01 +0000 Subject: [PATCH 120/469] Configure Style/MultilineBlockChain cop. --- .rubocop.yml | 6 ++++++ .rubocop_todo.yml | 5 ----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 03345f89..978750d6 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -16,6 +16,12 @@ Lint/UselessComparison: Exclude: - 'test/entry_test.rb' +# Allow this multi-line block chain as it actually reads better +# than the alternatives. +Style/MultilineBlockChain: + Exclude: + - 'lib/zip/crypto/traditional_encryption.rb' + # Allow inner slashes when using // for regex literals. Allow the # Guardfile to use a syntax that is more consistent with its own style. Style/RegexpLiteral: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index a0668e19..3bdb4a7d 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -183,11 +183,6 @@ Style/ModuleFunction: Exclude: - 'lib/zip.rb' -# Offense count: 1 -Style/MultilineBlockChain: - Exclude: - - 'lib/zip/crypto/traditional_encryption.rb' - # Offense count: 2 # Cop supports --auto-correct. Style/MultilineWhenThen: From 172ab4b567cc1171a9e19bccaf8421a6b9228016 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 22 Sep 2019 12:24:44 +0100 Subject: [PATCH 121/469] Fix Style/FloatDivision cop. --- .rubocop_todo.yml | 7 ------- samples/example.rb | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 3bdb4a7d..f853fe40 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -143,13 +143,6 @@ Style/EvalWithLocation: Exclude: - 'test/test_helper.rb' -# Offense count: 1 -# Configuration parameters: EnforcedStyle. -# SupportedStyles: left_coerce, right_coerce, single_coerce, fdiv -Style/FloatDivision: - Exclude: - - 'samples/example.rb' - # Offense count: 3 # Configuration parameters: . # SupportedStyles: annotated, template, unannotated diff --git a/samples/example.rb b/samples/example.rb index 224d4f1c..407ff6c6 100755 --- a/samples/example.rb +++ b/samples/example.rb @@ -71,7 +71,7 @@ # Track splitting an archive Zip::File.split('large_zip_file.zip', 1_048_576, true, 'part_zip_file') do |part_count, part_index, chunk_bytes, segment_bytes| - puts "#{part_index} of #{part_count} part splitting: #{(chunk_bytes.to_f / segment_bytes.to_f * 100).to_i}%" + puts "#{part_index} of #{part_count} part splitting: #{(chunk_bytes.to_f / segment_bytes * 100).to_i}%" end # For other examples, look at zip.rb and ziptest.rb From d42c66ce2cb03dab55be52bf5f769fb4e08d3d0a Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 22 Sep 2019 14:23:57 +0100 Subject: [PATCH 122/469] Fix Style/MultilineWhenThen cop. --- .rubocop_todo.yml | 6 ------ lib/zip/output_stream.rb | 4 ++-- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index f853fe40..9255d879 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -176,12 +176,6 @@ Style/ModuleFunction: Exclude: - 'lib/zip.rb' -# Offense count: 2 -# Cop supports --auto-correct. -Style/MultilineWhenThen: - Exclude: - - 'lib/zip/output_stream.rb' - # Offense count: 56 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. diff --git a/lib/zip/output_stream.rb b/lib/zip/output_stream.rb index 6ab4a484..fa60e855 100644 --- a/lib/zip/output_stream.rb +++ b/lib/zip/output_stream.rb @@ -150,9 +150,9 @@ def init_next_entry(entry, level = Zip.default_compression) def get_compressor(entry, level) case entry.compression_method - when Entry::DEFLATED then + when Entry::DEFLATED ::Zip::Deflater.new(@output_stream, level, @encrypter) - when Entry::STORED then + when Entry::STORED ::Zip::PassThruCompressor.new(@output_stream) else raise ::Zip::CompressionMethodError, From 2dc9b49568390b517e0f113c1fb5d932382d3238 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 22 Sep 2019 14:36:45 +0100 Subject: [PATCH 123/469] Fix Style/EvalWithLocation cop. --- .rubocop_todo.yml | 5 ----- test/test_helper.rb | 9 ++++++--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 9255d879..b0776ce2 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -138,11 +138,6 @@ Style/ClassCheck: Style/Documentation: Enabled: false -# Offense count: 2 -Style/EvalWithLocation: - Exclude: - - 'test/test_helper.rb' - # Offense count: 3 # Configuration parameters: . # SupportedStyles: annotated, template, unannotated diff --git a/test/test_helper.rb b/test/test_helper.rb index d1777bd7..ab52bad4 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -193,18 +193,21 @@ module ExtraAssertions def assert_forwarded(anObject, method, retVal, *expectedArgs) callArgs = nil setCallArgsProc = proc { |args| callArgs = args } - anObject.instance_eval <<-"end_eval" + anObject.instance_eval <<-END_EVAL, __FILE__, __LINE__ + 1 alias #{method}_org #{method} def #{method}(*args) ObjectSpace._id2ref(#{setCallArgsProc.object_id}).call(args) ObjectSpace._id2ref(#{retVal.object_id}) end - end_eval + END_EVAL assert_equal(retVal, yield) # Invoke test assert_equal(expectedArgs, callArgs) ensure - anObject.instance_eval "undef #{method}; alias #{method} #{method}_org" + anObject.instance_eval <<-END_EVAL, __FILE__, __LINE__ + 1 + undef #{method} + alias #{method} #{method}_org + END_EVAL end end From 835843d99223074fb845032f904ef4db2dcc77fc Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 22 Sep 2019 16:19:57 +0100 Subject: [PATCH 124/469] Fix Naming/HeredocDelimiterCase cop. --- .rubocop_todo.yml | 8 -------- lib/zip/filesystem.rb | 4 ++-- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b0776ce2..df5c84f6 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -69,14 +69,6 @@ Naming/BlockParameterName: - 'test/output_stream_test.rb' - 'test/test_helper.rb' -# Offense count: 2 -# Configuration parameters: EnforcedStyle. -# SupportedStyles: lowercase, uppercase -Naming/HeredocDelimiterCase: - Exclude: - - 'lib/zip/filesystem.rb' - - 'test/test_helper.rb' - # Offense count: 2 # Configuration parameters: EnforcedStyleForLeadingUnderscores. # SupportedStylesForLeadingUnderscores: disallowed, required, optional diff --git a/lib/zip/filesystem.rb b/lib/zip/filesystem.rb index abf98020..15c9c0c0 100644 --- a/lib/zip/filesystem.rb +++ b/lib/zip/filesystem.rb @@ -70,11 +70,11 @@ class ZipFsStat class << self def delegate_to_fs_file(*methods) methods.each do |method| - class_eval <<-end_eval, __FILE__, __LINE__ + 1 + class_eval <<-END_EVAL, __FILE__, __LINE__ + 1 def #{method} # def file? @zipFsFile.#{method}(@entryName) # @zipFsFile.file?(@entryName) end # end - end_eval + END_EVAL end end end From 7978abb85edab818a4d8a7e81e586d2dc17430bb Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 22 Sep 2019 17:32:00 +0100 Subject: [PATCH 125/469] Configure Naming/MemoizedInstanceVariableName cop. --- .rubocop.yml | 6 ++++++ .rubocop_todo.yml | 8 -------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 978750d6..d40eabcf 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -16,6 +16,12 @@ Lint/UselessComparison: Exclude: - 'test/entry_test.rb' +# Rubocop confuses these as instances of "memoization". +Naming/MemoizedInstanceVariableName: + Exclude: + - 'lib/zip/extra_field/old_unix.rb' + - 'lib/zip/extra_field/unix.rb' + # Allow this multi-line block chain as it actually reads better # than the alternatives. Style/MultilineBlockChain: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index df5c84f6..47c6e638 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -69,14 +69,6 @@ Naming/BlockParameterName: - 'test/output_stream_test.rb' - 'test/test_helper.rb' -# Offense count: 2 -# Configuration parameters: EnforcedStyleForLeadingUnderscores. -# SupportedStylesForLeadingUnderscores: disallowed, required, optional -Naming/MemoizedInstanceVariableName: - Exclude: - - 'lib/zip/extra_field/old_unix.rb' - - 'lib/zip/extra_field/unix.rb' - # Offense count: 140 # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. # AllowedNames: io, id, to, by, on, in, at, ip, db, os From 2f993221c0f67a94e05707fc67b1185dcaef8517 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 25 Sep 2019 22:44:48 +0100 Subject: [PATCH 126/469] Fix Style/Next cop. --- .rubocop_todo.yml | 8 -------- lib/zip/entry.rb | 12 ++++++------ 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 47c6e638..12fdade5 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -162,14 +162,6 @@ Style/ModuleFunction: Style/MutableConstant: Enabled: false -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, MinBodyLength. -# SupportedStyles: skip_modifier_ifs, always -Style/Next: - Exclude: - - 'lib/zip/entry.rb' - # Offense count: 2 # Cop supports --auto-correct. # Configuration parameters: EnforcedOctalStyle. diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 44b3319d..a3ad4997 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -625,13 +625,13 @@ def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exi while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf)) os << buf bytes_written += buf.bytesize - if bytes_written > size && !warned - message = "entry '#{name}' should be #{size}B, but is larger when inflated." - raise ::Zip::EntrySizeError, message if ::Zip.validate_entry_sizes + next unless bytes_written > size && !warned - warn "WARNING: #{message}" - warned = true - end + message = "entry '#{name}' should be #{size}B, but is larger when inflated." + raise ::Zip::EntrySizeError, message if ::Zip.validate_entry_sizes + + warn "WARNING: #{message}" + warned = true end end end From 2cbdbf110b6083d03d4f6715b7db1c28b6de1f91 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Thu, 26 Sep 2019 13:58:43 +0100 Subject: [PATCH 127/469] Fix Style/SignalException cop. --- .rubocop_todo.yml | 13 ------------- test/central_directory_entry_test.rb | 2 +- test/central_directory_test.rb | 4 ++-- test/filesystem/file_nonmutating_test.rb | 2 +- test/ioextras/abstract_input_stream_test.rb | 2 +- test/local_entry_test.rb | 2 +- test/test_helper.rb | 4 ++-- 7 files changed, 8 insertions(+), 21 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 12fdade5..213ad155 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -209,19 +209,6 @@ Style/SafeNavigation: - 'test/filesystem/file_stat_test.rb' - 'test/test_helper.rb' -# Offense count: 8 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: only_raise, only_fail, semantic -Style/SignalException: - Exclude: - - 'test/central_directory_entry_test.rb' - - 'test/central_directory_test.rb' - - 'test/filesystem/file_nonmutating_test.rb' - - 'test/ioextras/abstract_input_stream_test.rb' - - 'test/local_entry_test.rb' - - 'test/test_helper.rb' - # Offense count: 30 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. diff --git a/test/central_directory_entry_test.rb b/test/central_directory_entry_test.rb index fa0d8065..c060a4d3 100644 --- a/test/central_directory_entry_test.rb +++ b/test/central_directory_entry_test.rb @@ -63,7 +63,7 @@ def test_read_entry_from_truncated_zip_file fragment.extend(IOizeString) entry = ::Zip::Entry.new entry.read_c_dir_entry(fragment) - fail 'ZipError expected' + raise 'ZipError expected' rescue ::Zip::Error end end diff --git a/test/central_directory_test.rb b/test/central_directory_test.rb index 26be6424..28e12f4a 100644 --- a/test/central_directory_test.rb +++ b/test/central_directory_test.rb @@ -22,7 +22,7 @@ def test_read_from_invalid_stream cdir = ::Zip::CentralDirectory.new cdir.read_from_stream(zipFile) end - fail 'ZipError expected!' + raise 'ZipError expected!' rescue ::Zip::Error end @@ -33,7 +33,7 @@ def test_read_from_truncated_zip_file fragment.extend(IOizeString) entry = ::Zip::CentralDirectory.new entry.read_from_stream(fragment) - fail 'ZipError expected' + raise 'ZipError expected' rescue ::Zip::Error end diff --git a/test/filesystem/file_nonmutating_test.rb b/test/filesystem/file_nonmutating_test.rb index af575fac..73161cf6 100644 --- a/test/filesystem/file_nonmutating_test.rb +++ b/test/filesystem/file_nonmutating_test.rb @@ -80,7 +80,7 @@ def test_new end begin is = @zip_file.file.new('file1') do - fail 'should not call block' + raise 'should not call block' end ensure is.close if is diff --git a/test/ioextras/abstract_input_stream_test.rb b/test/ioextras/abstract_input_stream_test.rb index 3ae005d1..46925a8d 100644 --- a/test/ioextras/abstract_input_stream_test.rb +++ b/test/ioextras/abstract_input_stream_test.rb @@ -95,7 +95,7 @@ def test_readline test_gets begin @io.readline - fail 'EOFError expected' + raise 'EOFError expected' rescue EOFError end end diff --git a/test/local_entry_test.rb b/test/local_entry_test.rb index 666a63a0..b02deb67 100644 --- a/test/local_entry_test.rb +++ b/test/local_entry_test.rb @@ -46,7 +46,7 @@ def test_read_local_entry_from_truncated_zip_file zipFragment.extend(IOizeString).reset entry = ::Zip::Entry.new entry.read_local_entry(zipFragment) - fail 'ZipError expected' + raise 'ZipError expected' rescue ::Zip::Error end diff --git a/test/test_helper.rb b/test/test_helper.rb index ab52bad4..a8b719f0 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -95,7 +95,7 @@ def assert_entry_contents_for_stream(filename, zis, entryName) if (expected && actual) && (expected.length > 400 || actual.length > 400) zipEntryFilename = entryName + '.zipEntry' File.open(zipEntryFilename, 'wb') { |entryfile| entryfile << actual } - fail("File '#{filename}' is different from '#{zipEntryFilename}'") + raise("File '#{filename}' is different from '#{zipEntryFilename}'") else assert_equal(expected, actual) end @@ -111,7 +111,7 @@ def self.assert_contents(filename, aString) if fileContents.length > 400 || aString.length > 400 stringFile = filename + '.other' File.open(stringFile, 'wb') { |f| f << aString } - fail("File '#{filename}' is different from contents of string stored in '#{stringFile}'") + raise("File '#{filename}' is different from contents of string stored in '#{stringFile}'") else assert_equal(fileContents, aString) end From bb3b4474fa1c034aa9ef749fec5679045f78f5f1 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Thu, 26 Sep 2019 21:46:00 +0100 Subject: [PATCH 128/469] Fix Style/SpecialGlobalVars cop. --- .rubocop_todo.yml | 21 -------------------- lib/zip.rb | 1 + lib/zip/filesystem.rb | 2 +- lib/zip/ioextras/abstract_input_stream.rb | 12 +++++------ lib/zip/ioextras/abstract_output_stream.rb | 2 +- samples/example.rb | 2 +- samples/example_filesystem.rb | 2 +- samples/gtk_ruby_zip.rb | 2 +- samples/qtzip.rb | 4 ++-- samples/write_simple.rb | 2 +- samples/zipfind.rb | 6 +++--- test/gentestfiles.rb | 2 +- test/ioextras/abstract_input_stream_test.rb | 6 +++--- test/ioextras/abstract_output_stream_test.rb | 4 ++-- test/output_stream_test.rb | 8 ++++---- test/test_helper.rb | 2 +- 16 files changed, 29 insertions(+), 49 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 213ad155..ededff08 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -209,27 +209,6 @@ Style/SafeNavigation: - 'test/filesystem/file_stat_test.rb' - 'test/test_helper.rb' -# Offense count: 30 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: use_perl_names, use_english_names -Style/SpecialGlobalVars: - Exclude: - - 'lib/zip/filesystem.rb' - - 'lib/zip/ioextras/abstract_input_stream.rb' - - 'lib/zip/ioextras/abstract_output_stream.rb' - - 'samples/example.rb' - - 'samples/example_filesystem.rb' - - 'samples/gtk_ruby_zip.rb' - - 'samples/qtzip.rb' - - 'samples/write_simple.rb' - - 'samples/zipfind.rb' - - 'test/gentestfiles.rb' - - 'test/ioextras/abstract_input_stream_test.rb' - - 'test/ioextras/abstract_output_stream_test.rb' - - 'test/output_stream_test.rb' - - 'test/test_helper.rb' - # Offense count: 42 # Cop supports --auto-correct. # Configuration parameters: IgnoredMethods. diff --git a/lib/zip.rb b/lib/zip.rb index 5261fd77..8cf982a5 100644 --- a/lib/zip.rb +++ b/lib/zip.rb @@ -1,3 +1,4 @@ +require 'English' require 'delegate' require 'singleton' require 'tempfile' diff --git a/lib/zip/filesystem.rb b/lib/zip/filesystem.rb index 15c9c0c0..2a5dbdf5 100644 --- a/lib/zip/filesystem.rb +++ b/lib/zip/filesystem.rb @@ -401,7 +401,7 @@ def popen(*args, &aProc) ::File.popen(*args, &aProc) end - def foreach(fileName, aSep = $/, &aProc) + def foreach(fileName, aSep = $INPUT_RECORD_SEPARATOR, &aProc) self.open(fileName) { |is| is.each_line(aSep, &aProc) } end diff --git a/lib/zip/ioextras/abstract_input_stream.rb b/lib/zip/ioextras/abstract_input_stream.rb index 85f4974e..8392d240 100644 --- a/lib/zip/ioextras/abstract_input_stream.rb +++ b/lib/zip/ioextras/abstract_input_stream.rb @@ -49,13 +49,13 @@ def read(number_of_bytes = nil, buf = '') buf end - def readlines(a_sep_string = $/) + def readlines(a_sep_string = $INPUT_RECORD_SEPARATOR) ret_val = [] each_line(a_sep_string) { |line| ret_val << line } ret_val end - def gets(a_sep_string = $/, number_of_bytes = nil) + def gets(a_sep_string = $INPUT_RECORD_SEPARATOR, number_of_bytes = nil) @lineno = @lineno.next if number_of_bytes.respond_to?(:to_int) @@ -63,7 +63,7 @@ def gets(a_sep_string = $/, number_of_bytes = nil) a_sep_string = a_sep_string.to_str if a_sep_string elsif a_sep_string.respond_to?(:to_int) number_of_bytes = a_sep_string.to_int - a_sep_string = $/ + a_sep_string = $INPUT_RECORD_SEPARATOR else number_of_bytes = nil a_sep_string = a_sep_string.to_str if a_sep_string @@ -71,7 +71,7 @@ def gets(a_sep_string = $/, number_of_bytes = nil) return read(number_of_bytes) if a_sep_string.nil? - a_sep_string = "#{$/}#{$/}" if a_sep_string.empty? + a_sep_string = "#{$INPUT_RECORD_SEPARATOR}#{$INPUT_RECORD_SEPARATOR}" if a_sep_string.empty? buffer_index = 0 over_limit = (number_of_bytes && @output_buffer.bytesize >= number_of_bytes) @@ -97,14 +97,14 @@ def flush ret_val end - def readline(a_sep_string = $/) + def readline(a_sep_string = $INPUT_RECORD_SEPARATOR) ret_val = gets(a_sep_string) raise EOFError unless ret_val ret_val end - def each_line(a_sep_string = $/) + def each_line(a_sep_string = $INPUT_RECORD_SEPARATOR) loop { yield readline(a_sep_string) } rescue EOFError # We just need to catch this; we don't need to handle it. diff --git a/lib/zip/ioextras/abstract_output_stream.rb b/lib/zip/ioextras/abstract_output_stream.rb index 69d0cc7c..b94c9d49 100644 --- a/lib/zip/ioextras/abstract_output_stream.rb +++ b/lib/zip/ioextras/abstract_output_stream.rb @@ -11,7 +11,7 @@ def write(data) end def print(*params) - self << params.join($,) << $\.to_s + self << params.join($OUTPUT_FIELD_SEPARATOR) << $OUTPUT_RECORD_SEPARATOR.to_s end def printf(a_format_string, *params) diff --git a/samples/example.rb b/samples/example.rb index 407ff6c6..345e7e19 100755 --- a/samples/example.rb +++ b/samples/example.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -$: << '../lib' +$LOAD_PATH << '../lib' system('zip example.zip example.rb gtk_ruby_zip.rb') require 'zip' diff --git a/samples/example_filesystem.rb b/samples/example_filesystem.rb index f253a5e5..0d93ab6b 100755 --- a/samples/example_filesystem.rb +++ b/samples/example_filesystem.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -$: << '../lib' +$LOAD_PATH << '../lib' require 'zip/filesystem' diff --git a/samples/gtk_ruby_zip.rb b/samples/gtk_ruby_zip.rb index 62f005a5..4ce1cae0 100755 --- a/samples/gtk_ruby_zip.rb +++ b/samples/gtk_ruby_zip.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -$: << '../lib' +$LOAD_PATH << '../lib' $VERBOSE = true diff --git a/samples/qtzip.rb b/samples/qtzip.rb index 1d450a78..f76f57a7 100755 --- a/samples/qtzip.rb +++ b/samples/qtzip.rb @@ -2,7 +2,7 @@ $VERBOSE = true -$: << '../lib' +$LOAD_PATH << '../lib' require 'Qt' system('rbuic -o zipdialogui.rb zipdialogui.ui') @@ -80,7 +80,7 @@ def extract_files end unless ARGV[0] - puts "usage: #{$0} zipname" + puts "usage: #{$PROGRAM_NAME} zipname" exit end diff --git a/samples/write_simple.rb b/samples/write_simple.rb index 0c534ede..8bb31bb3 100755 --- a/samples/write_simple.rb +++ b/samples/write_simple.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -$: << '../lib' +$LOAD_PATH << '../lib' require 'zip' diff --git a/samples/zipfind.rb b/samples/zipfind.rb index 59ca1fdb..a88bc42d 100755 --- a/samples/zipfind.rb +++ b/samples/zipfind.rb @@ -2,7 +2,7 @@ $VERBOSE = true -$: << '../lib' +$LOAD_PATH << '../lib' require 'zip' require 'find' @@ -32,7 +32,7 @@ def self.find_file(path, fileNamePattern, zipFilePattern = /\.zip$/i) end end -if $0 == __FILE__ +if $PROGRAM_NAME == __FILE__ module ZipFindConsoleRunner PATH_ARG_INDEX = 0 FILENAME_PATTERN_ARG_INDEX = 1 @@ -55,7 +55,7 @@ def self.check_args(args) end def self.usage - puts "Usage: #{$0} PATH ZIPFILENAME_PATTERN FILNAME_PATTERN" + puts "Usage: #{$PROGRAM_NAME} PATH ZIPFILENAME_PATTERN FILNAME_PATTERN" end def self.report_entry_found(fileName) diff --git a/test/gentestfiles.rb b/test/gentestfiles.rb index a50f51d5..3427ad42 100755 --- a/test/gentestfiles.rb +++ b/test/gentestfiles.rb @@ -112,7 +112,7 @@ def self.create_test_zips # to help create the following files, there's a free one available from # http://stahlworks.com/dev/index.php?tool=zipunzip # that works with the above code - raise $!.to_s + + raise $ERROR_INFO.to_s + "\n\nziptest.rb requires the Info-ZIP program 'zip' in the path\n" \ "to create test data. If you don't have it you can download\n" \ 'the necessary test files at http://sf.net/projects/rubyzip.' diff --git a/test/ioextras/abstract_input_stream_test.rb b/test/ioextras/abstract_input_stream_test.rb index 46925a8d..f8986b84 100644 --- a/test/ioextras/abstract_input_stream_test.rb +++ b/test/ioextras/abstract_input_stream_test.rb @@ -4,8 +4,8 @@ class AbstractInputStreamTest < MiniTest::Test # AbstractInputStream subclass that provides a read method - TEST_LINES = ["Hello world#{$/}", - "this is the second line#{$/}", + TEST_LINES = ["Hello world#{$INPUT_RECORD_SEPARATOR}", + "this is the second line#{$INPUT_RECORD_SEPARATOR}", 'this is the last line'] TEST_STRING = TEST_LINES.join class TestAbstractInputStream @@ -50,7 +50,7 @@ def test_gets def test_gets_multi_char_seperator assert_equal('Hell', @io.gets('ll')) - assert_equal("o world#{$/}this is the second l", @io.gets('d l')) + assert_equal("o world#{$INPUT_RECORD_SEPARATOR}this is the second l", @io.gets('d l')) end LONG_LINES = [ diff --git a/test/ioextras/abstract_output_stream_test.rb b/test/ioextras/abstract_output_stream_test.rb index 3077db43..17b31a78 100644 --- a/test/ioextras/abstract_output_stream_test.rb +++ b/test/ioextras/abstract_output_stream_test.rb @@ -20,8 +20,8 @@ def <<(data) def setup @output_stream = TestOutputStream.new - @origCommaSep = $, - @origOutputSep = $\ + @origCommaSep = $OUTPUT_FIELD_SEPARATOR + @origOutputSep = $OUTPUT_RECORD_SEPARATOR end def teardown diff --git a/test/output_stream_test.rb b/test/output_stream_test.rb index f3875266..106ba6af 100644 --- a/test/output_stream_test.rb +++ b/test/output_stream_test.rb @@ -58,10 +58,10 @@ def test_cannot_open_file begin ::Zip::OutputStream.open(name) rescue SystemCallError - assert($!.kind_of?(Errno::EISDIR) || # Linux - $!.kind_of?(Errno::EEXIST) || # Windows/cygwin - $!.kind_of?(Errno::EACCES), # Windows - "Expected Errno::EISDIR (or on win/cygwin: Errno::EEXIST), but was: #{$!.class}") + assert($ERROR_INFO.kind_of?(Errno::EISDIR) || # Linux + $ERROR_INFO.kind_of?(Errno::EEXIST) || # Windows/cygwin + $ERROR_INFO.kind_of?(Errno::EACCES), # Windows + "Expected Errno::EISDIR (or on win/cygwin: Errno::EEXIST), but was: #{$ERROR_INFO.class}") end end diff --git a/test/test_helper.rb b/test/test_helper.rb index a8b719f0..0e14b6da 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -61,7 +61,7 @@ module DecompressorTests def setup @refText = '' File.open(TEST_FILE) { |f| @refText = f.read } - @refLines = @refText.split($/) + @refLines = @refText.split($INPUT_RECORD_SEPARATOR) end def test_read_everything From 0df6cb3059a9d770ac4fd800ad3fdb45872192cb Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Thu, 26 Sep 2019 22:38:28 +0100 Subject: [PATCH 129/469] Fix Style/SymbolProc cop. --- .rubocop_todo.yml | 16 ------- lib/zip/file.rb | 2 +- lib/zip/filesystem.rb | 2 +- test/basic_zip_file_test.rb | 2 +- test/case_sensitivity_test.rb | 7 +-- test/deflater_test.rb | 2 +- test/file_extract_test.rb | 4 +- test/file_split_test.rb | 4 +- test/file_test.rb | 57 +++++++++++++----------- test/filesystem/file_nonmutating_test.rb | 8 ++-- 10 files changed, 46 insertions(+), 58 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index ededff08..9f090fac 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -209,22 +209,6 @@ Style/SafeNavigation: - 'test/filesystem/file_stat_test.rb' - 'test/test_helper.rb' -# Offense count: 42 -# Cop supports --auto-correct. -# Configuration parameters: IgnoredMethods. -# IgnoredMethods: respond_to, define_method -Style/SymbolProc: - Exclude: - - 'lib/zip/file.rb' - - 'lib/zip/filesystem.rb' - - 'test/basic_zip_file_test.rb' - - 'test/case_sensitivity_test.rb' - - 'test/deflater_test.rb' - - 'test/file_extract_test.rb' - - 'test/file_split_test.rb' - - 'test/file_test.rb' - - 'test/filesystem/file_nonmutating_test.rb' - # Offense count: 3 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, AllowSafeAssignment. diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 346bbad6..37dcd921 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -288,7 +288,7 @@ def to_s # Returns a string containing the contents of the specified entry def read(entry) - get_input_stream(entry) { |is| is.read } + get_input_stream(entry, &:read) end # Convenience method for adding the contents of a file to the archive diff --git a/lib/zip/filesystem.rb b/lib/zip/filesystem.rb index 2a5dbdf5..4f3e9954 100644 --- a/lib/zip/filesystem.rb +++ b/lib/zip/filesystem.rb @@ -390,7 +390,7 @@ def stat(fileName) alias lstat stat def readlines(fileName) - self.open(fileName) { |is| is.readlines } + self.open(fileName, &:readlines) end def read(fileName) diff --git a/test/basic_zip_file_test.rb b/test/basic_zip_file_test.rb index 9e490b4a..3d21ae89 100644 --- a/test/basic_zip_file_test.rb +++ b/test/basic_zip_file_test.rb @@ -10,7 +10,7 @@ def setup def test_entries assert_equal(TestZipFile::TEST_ZIP2.entry_names.sort, - @zip_file.entries.entries.sort.map { |e| e.name }) + @zip_file.entries.entries.sort.map(&:name)) end def test_each diff --git a/test/case_sensitivity_test.rb b/test/case_sensitivity_test.rb index 7749e06b..92fc0f6c 100644 --- a/test/case_sensitivity_test.rb +++ b/test/case_sensitivity_test.rb @@ -25,7 +25,7 @@ def test_add_case_sensitive SRC_FILES.each_with_index do |a, i| assert_equal(a.last, zfRead.entries[i].name) AssertEntry.assert_contents(a.first, - zfRead.get_input_stream(a.last) { |zis| zis.read }) + zfRead.get_input_stream(a.last, &:read)) end end @@ -56,8 +56,9 @@ def test_add_case_sensitive_read_case_insensitive zfRead = ::Zip::File.new(EMPTY_FILENAME) assert_equal(SRC_FILES.collect { |_fn, en| en.downcase }.uniq.size, zfRead.entries.length) assert_equal(SRC_FILES.last.last.downcase, zfRead.entries.first.name.downcase) - AssertEntry.assert_contents(SRC_FILES.last.first, - zfRead.get_input_stream(SRC_FILES.last.last) { |zis| zis.read }) + AssertEntry.assert_contents( + SRC_FILES.last.first, zfRead.get_input_stream(SRC_FILES.last.last, &:read) + ) end private diff --git a/test/deflater_test.rb b/test/deflater_test.rb index d1970ce9..78c22dfc 100644 --- a/test/deflater_test.rb +++ b/test/deflater_test.rb @@ -43,7 +43,7 @@ def test_data_error private def load_file(fileName) - File.open(fileName, 'rb') { |f| f.read } + File.open(fileName, 'rb', &:read) end def deflate(data, fileName) diff --git a/test/file_extract_test.rb b/test/file_extract_test.rb index a494f781..3992a1ad 100644 --- a/test/file_extract_test.rb +++ b/test/file_extract_test.rb @@ -20,7 +20,7 @@ def test_extract assert(File.exist?(EXTRACTED_FILENAME)) AssertEntry.assert_contents(EXTRACTED_FILENAME, - zf.get_input_stream(ENTRY_TO_EXTRACT) { |is| is.read }) + zf.get_input_stream(ENTRY_TO_EXTRACT, &:read)) ::File.unlink(EXTRACTED_FILENAME) @@ -29,7 +29,7 @@ def test_extract assert(File.exist?(EXTRACTED_FILENAME)) AssertEntry.assert_contents(EXTRACTED_FILENAME, - entry.get_input_stream { |is| is.read }) + entry.get_input_stream(&:read)) end end diff --git a/test/file_split_test.rb b/test/file_split_test.rb index c488f180..22dd1348 100644 --- a/test/file_split_test.rb +++ b/test/file_split_test.rb @@ -43,7 +43,7 @@ def test_split assert(File.exist?(EXTRACTED_FILENAME)) AssertEntry.assert_contents(EXTRACTED_FILENAME, - zf.get_input_stream(ENTRY_TO_EXTRACT) { |is| is.read }) + zf.get_input_stream(ENTRY_TO_EXTRACT, &:read)) File.unlink(EXTRACTED_FILENAME) @@ -52,7 +52,7 @@ def test_split assert(File.exist?(EXTRACTED_FILENAME)) AssertEntry.assert_contents(EXTRACTED_FILENAME, - entry.get_input_stream { |is| is.read }) + entry.get_input_stream(&:read)) end end end diff --git a/test/file_test.rb b/test/file_test.rb index ba3f0eac..21aa72f7 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -106,14 +106,14 @@ def test_get_output_stream def test_open_buffer_with_string string = File.read('test/data/rubycode.zip') ::Zip::File.open_buffer string do |zf| - assert zf.entries.map { |e| e.name }.include?('zippedruby1.rb') + assert zf.entries.map(&:name).include?('zippedruby1.rb') end end def test_open_buffer_with_stringio string_io = StringIO.new File.read('test/data/rubycode.zip') ::Zip::File.open_buffer string_io do |zf| - assert zf.entries.map { |e| e.name }.include?('zippedruby1.rb') + assert zf.entries.map(&:name).include?('zippedruby1.rb') end end @@ -171,7 +171,7 @@ def test_open_buffer_with_io_and_block def test_open_buffer_without_block string_io = StringIO.new File.read('test/data/rubycode.zip') zf = ::Zip::File.open_buffer string_io - assert zf.entries.map { |e| e.name }.include?('zippedruby1.rb') + assert zf.entries.map(&:name).include?('zippedruby1.rb') end def test_cleans_up_tempfiles_after_close @@ -201,7 +201,7 @@ def test_add assert_equal(1, zfRead.entries.length) assert_equal(entryName, zfRead.entries.first.name) AssertEntry.assert_contents(srcFile, - zfRead.get_input_stream(entryName) { |zis| zis.read }) + zfRead.get_input_stream(entryName, &:read)) end def test_add_stored @@ -221,7 +221,7 @@ def test_add_stored assert_equal(entry.size, entry.compressed_size) assert_equal(::Zip::Entry::STORED, entry.compression_method) AssertEntry.assert_contents(srcFile, - zfRead.get_input_stream(entryName) { |zis| zis.read }) + zfRead.get_input_stream(entryName, &:read)) end def test_recover_permissions_after_add_files_to_archive @@ -277,15 +277,15 @@ def test_remove FileUtils.cp(TestZipFile::TEST_ZIP2.zip_name, TEST_ZIP.zip_name) zf = ::Zip::File.new(TEST_ZIP.zip_name) - assert(zf.entries.map { |e| e.name }.include?(entryToRemove)) + assert(zf.entries.map(&:name).include?(entryToRemove)) zf.remove(entryToRemove) - assert(!zf.entries.map { |e| e.name }.include?(entryToRemove)) - assert_equal(zf.entries.map { |x| x.name }.sort, remainingEntries.sort) + assert(!zf.entries.map(&:name).include?(entryToRemove)) + assert_equal(zf.entries.map(&:name).sort, remainingEntries.sort) zf.close zfRead = ::Zip::File.new(TEST_ZIP.zip_name) - assert(!zfRead.entries.map { |e| e.name }.include?(entryToRemove)) - assert_equal(zfRead.entries.map { |x| x.name }.sort, remainingEntries.sort) + assert(!zfRead.entries.map(&:name).include?(entryToRemove)) + assert_equal(zfRead.entries.map(&:name).sort, remainingEntries.sort) zfRead.close end @@ -293,21 +293,21 @@ def test_rename entryToRename, * = TEST_ZIP.entry_names zf = ::Zip::File.new(TEST_ZIP.zip_name) - assert(zf.entries.map { |e| e.name }.include?(entryToRename)) + assert(zf.entries.map(&:name).include?(entryToRename)) contents = zf.read(entryToRename) newName = 'changed entry name' - assert(!zf.entries.map { |e| e.name }.include?(newName)) + assert(!zf.entries.map(&:name).include?(newName)) zf.rename(entryToRename, newName) - assert(zf.entries.map { |e| e.name }.include?(newName)) + assert(zf.entries.map(&:name).include?(newName)) assert_equal(contents, zf.read(newName)) zf.close zfRead = ::Zip::File.new(TEST_ZIP.zip_name) - assert(zfRead.entries.map { |e| e.name }.include?(newName)) + assert(zfRead.entries.map(&:name).include?(newName)) assert_equal(contents, zfRead.read(newName)) zfRead.close end @@ -352,7 +352,7 @@ def test_rename_to_existing_entry end ::Zip::File.open(TEST_ZIP.zip_name) do |zf| - assert_equal(oldEntries.sort.map { |e| e.name }, zf.entries.sort.map { |e| e.name }) + assert_equal(oldEntries.sort.map(&:name), zf.entries.sort.map(&:name)) end end @@ -373,8 +373,8 @@ def test_rename_to_existing_entry_overwrite assert(gotCalled) oldEntries.delete_if { |e| e.name == renamedEntryName } ::Zip::File.open(TEST_ZIP.zip_name) do |zf| - assert_equal(oldEntries.sort.map { |e| e.name }, - zf.entries.sort.map { |e| e.name }) + assert_equal(oldEntries.sort.map(&:name), + zf.entries.sort.map(&:name)) end end @@ -407,13 +407,16 @@ def test_replace zf.close zfRead = ::Zip::File.new(TEST_ZIP.zip_name) AssertEntry.assert_contents(newEntrySrcFilename, - zfRead.get_input_stream(entryToReplace) { |is| is.read }) + zfRead.get_input_stream(entryToReplace, &:read)) AssertEntry.assert_contents(TEST_ZIP.entry_names[0], - zfRead.get_input_stream(TEST_ZIP.entry_names[0]) { |is| is.read }) + zfRead.get_input_stream(TEST_ZIP.entry_names[0], + &:read)) AssertEntry.assert_contents(TEST_ZIP.entry_names[1], - zfRead.get_input_stream(TEST_ZIP.entry_names[1]) { |is| is.read }) + zfRead.get_input_stream(TEST_ZIP.entry_names[1], + &:read)) AssertEntry.assert_contents(TEST_ZIP.entry_names[3], - zfRead.get_input_stream(TEST_ZIP.entry_names[3]) { |is| is.read }) + zfRead.get_input_stream(TEST_ZIP.entry_names[3], + &:read)) zfRead.close end @@ -555,7 +558,7 @@ def test_compound2 zf.add(filename, filename) assert_contains(zf, filename) end - assert_equal(zf.entries.sort.map { |e| e.name }, TestFiles::ASCII_TEST_FILES) + assert_equal(zf.entries.sort.map(&:name), TestFiles::ASCII_TEST_FILES) zf.rename(TestFiles::ASCII_TEST_FILES[0], 'newName') assert_not_contains(zf, TestFiles::ASCII_TEST_FILES[0]) @@ -588,7 +591,7 @@ def test_change_comment def test_preserve_file_order entryNames = nil ::Zip::File.open(TEST_ZIP.zip_name) do |zf| - entryNames = zf.entries.map { |e| e.to_s } + entryNames = zf.entries.map(&:to_s) zf.get_output_stream('a.txt') { |os| os.write 'this is a.txt' } zf.get_output_stream('z.txt') { |os| os.write 'this is z.txt' } zf.get_output_stream('k.txt') { |os| os.write 'this is k.txt' } @@ -596,16 +599,16 @@ def test_preserve_file_order end ::Zip::File.open(TEST_ZIP.zip_name) do |zf| - assert_equal(entryNames, zf.entries.map { |e| e.to_s }) - entries = zf.entries.sort_by { |e| e.name }.reverse + assert_equal(entryNames, zf.entries.map(&:to_s)) + entries = zf.entries.sort_by(&:name).reverse entries.each do |e| zf.remove e zf.get_output_stream(e) { |os| os.write 'foo' } end - entryNames = entries.map { |e| e.to_s } + entryNames = entries.map(&:to_s) end ::Zip::File.open(TEST_ZIP.zip_name) do |zf| - assert_equal(entryNames, zf.entries.map { |e| e.to_s }) + assert_equal(entryNames, zf.entries.map(&:to_s)) end end diff --git a/test/filesystem/file_nonmutating_test.rb b/test/filesystem/file_nonmutating_test.rb index 73161cf6..af4db8f2 100644 --- a/test/filesystem/file_nonmutating_test.rb +++ b/test/filesystem/file_nonmutating_test.rb @@ -468,12 +468,12 @@ def test_popen if Zip::RUNNING_ON_WINDOWS # This is pretty much projectile vomit but it allows the test to be # run on windows also - system_dir = ::File.popen('dir') { |f| f.read }.gsub(/Dir\(s\).*$/, '') - zipfile_dir = @zip_file.file.popen('dir') { |f| f.read }.gsub(/Dir\(s\).*$/, '') + system_dir = ::File.popen('dir', &:read).gsub(/Dir\(s\).*$/, '') + zipfile_dir = @zip_file.file.popen('dir', &:read).gsub(/Dir\(s\).*$/, '') assert_equal(system_dir, zipfile_dir) else - assert_equal(::File.popen('ls') { |f| f.read }, - @zip_file.file.popen('ls') { |f| f.read }) + assert_equal(::File.popen('ls', &:read), + @zip_file.file.popen('ls', &:read)) end end From 6cab5922bc778cc89108c6bd3144383ca4d32217 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Thu, 26 Sep 2019 22:00:46 +0100 Subject: [PATCH 130/469] Configure and fix Metrics/LineLength cop. Set a workable line length for now, and fix a couple of particularly bad examples. Also, turn off for the tests. --- .rubocop.yml | 7 ++++++ .rubocop_todo.yml | 9 ++------ lib/zip/file.rb | 6 ++++- lib/zip/output_stream.rb | 4 +++- test/gentestfiles.rb | 46 ++++++++++++++++++++++++++++++-------- test/output_stream_test.rb | 3 ++- 6 files changed, 56 insertions(+), 19 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index d40eabcf..4fe77b94 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -4,6 +4,13 @@ Layout/HashAlignment: EnforcedHashRocketStyle: table EnforcedColonStyle: table +# Set a workable line length, given the current state of the code, +# and turn off for the tests. +Layout/LineLength: + Max: 135 + Exclude: + - 'test/**/*.rb' + # In some cases we just need to catch an exception, rather than # actually handle it. Allow the tests to make use of this shortcut. Lint/SuppressedException: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 9f090fac..8ed38aa7 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -37,6 +37,8 @@ Metrics/CyclomaticComplexity: # Configuration parameters: CountComments, ExcludedMethods. Metrics/MethodLength: Max: 45 + Exclude: + - 'test/**/*.rb' # Offense count: 2 # Configuration parameters: CountKeywordArgs. @@ -226,10 +228,3 @@ Style/TernaryParentheses: Style/TrailingCommaInHashLiteral: Exclude: - 'lib/zip/constants.rb' - -# Offense count: 247 -# Cop supports --auto-correct. -# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. -# URISchemes: http, https -Layout/LineLength: - Max: 236 diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 37dcd921..2d274081 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -264,7 +264,11 @@ def get_input_stream(entry, &aProc) # specified. If a block is passed the stream object is passed to the block and # the stream is automatically closed afterwards just as with ruby's builtin # File.open method. - def get_output_stream(entry, permission_int = nil, comment = nil, extra = nil, compressed_size = nil, crc = nil, compression_method = nil, size = nil, time = nil, &aProc) + def get_output_stream(entry, permission_int = nil, comment = nil, + extra = nil, compressed_size = nil, crc = nil, + compression_method = nil, size = nil, time = nil, + &aProc) + new_entry = if entry.kind_of?(Entry) entry diff --git a/lib/zip/output_stream.rb b/lib/zip/output_stream.rb index fa60e855..978f4b01 100644 --- a/lib/zip/output_stream.rb +++ b/lib/zip/output_stream.rb @@ -130,7 +130,9 @@ def finalize_current_entry return unless @current_entry finish - @current_entry.compressed_size = @output_stream.tell - @current_entry.local_header_offset - @current_entry.calculate_local_header_size + @current_entry.compressed_size = @output_stream.tell - \ + @current_entry.local_header_offset - \ + @current_entry.calculate_local_header_size @current_entry.size = @compressor.size @current_entry.crc = @compressor.crc @output_stream << @encrypter.data_descriptor(@current_entry.crc, @current_entry.compressed_size, @current_entry.size) diff --git a/test/gentestfiles.rb b/test/gentestfiles.rb index 3427ad42..acdada45 100755 --- a/test/gentestfiles.rb +++ b/test/gentestfiles.rb @@ -72,8 +72,12 @@ def initialize(zip_name, entry_names, comment = '') end def self.create_test_zips - raise "failed to create test zip '#{TEST_ZIP1.zip_name}'" unless system("/usr/bin/zip -q #{TEST_ZIP1.zip_name} test/data/file2.txt") - raise "failed to remove entry from '#{TEST_ZIP1.zip_name}'" unless system("/usr/bin/zip -q #{TEST_ZIP1.zip_name} -d test/data/file2.txt") + raise "failed to create test zip '#{TEST_ZIP1.zip_name}'" \ + unless system("/usr/bin/zip -q #{TEST_ZIP1.zip_name} test/data/file2.txt") + raise "failed to remove entry from '#{TEST_ZIP1.zip_name}'" \ + unless system( + "/usr/bin/zip -q #{TEST_ZIP1.zip_name} -d test/data/file2.txt" + ) File.open('test/data/generated/empty.txt', 'w') {} File.open('test/data/generated/empty_chmod640.txt', 'w') {} @@ -94,19 +98,34 @@ def self.create_test_zips file << testBinaryPattern << rand << "\0" while file.tell < 6E5 end - raise "failed to create test zip '#{TEST_ZIP2.zip_name}'" unless system("/usr/bin/zip -q #{TEST_ZIP2.zip_name} #{TEST_ZIP2.entry_names.join(' ')}") + raise "failed to create test zip '#{TEST_ZIP2.zip_name}'" \ + unless system( + "/usr/bin/zip -q #{TEST_ZIP2.zip_name} #{TEST_ZIP2.entry_names.join(' ')}" + ) if RUBY_PLATFORM =~ /mswin|mingw|cygwin/ - raise "failed to add comment to test zip '#{TEST_ZIP2.zip_name}'" unless system("echo #{TEST_ZIP2.comment}| /usr/bin/zip -zq #{TEST_ZIP2.zip_name}\"") + raise "failed to add comment to test zip '#{TEST_ZIP2.zip_name}'" \ + unless system( + "echo #{TEST_ZIP2.comment}| /usr/bin/zip -zq #{TEST_ZIP2.zip_name}\"" + ) else # without bash system interprets everything after echo as parameters to # echo including | zip -z ... - raise "failed to add comment to test zip '#{TEST_ZIP2.zip_name}'" unless system("bash -c \"echo #{TEST_ZIP2.comment} | /usr/bin/zip -zq #{TEST_ZIP2.zip_name}\"") + raise "failed to add comment to test zip '#{TEST_ZIP2.zip_name}'" \ + unless system( + "bash -c \"echo #{TEST_ZIP2.comment} | /usr/bin/zip -zq #{TEST_ZIP2.zip_name}\"" + ) end - raise "failed to create test zip '#{TEST_ZIP3.zip_name}'" unless system("/usr/bin/zip -q #{TEST_ZIP3.zip_name} #{TEST_ZIP3.entry_names.join(' ')}") + raise "failed to create test zip '#{TEST_ZIP3.zip_name}'" \ + unless system( + "/usr/bin/zip -q #{TEST_ZIP3.zip_name} #{TEST_ZIP3.entry_names.join(' ')}" + ) - raise "failed to create test zip '#{TEST_ZIP4.zip_name}'" unless system("/usr/bin/zip -q #{TEST_ZIP4.zip_name} #{TEST_ZIP4.entry_names.join(' ')}") + raise "failed to create test zip '#{TEST_ZIP4.zip_name}'" \ + unless system( + "/usr/bin/zip -q #{TEST_ZIP4.zip_name} #{TEST_ZIP4.entry_names.join(' ')}" + ) rescue StandardError # If there are any Windows developers wanting to use a command line zip.exe # to help create the following files, there's a free one available from @@ -119,8 +138,17 @@ def self.create_test_zips end TEST_ZIP1 = TestZipFile.new('test/data/generated/empty.zip', []) - TEST_ZIP2 = TestZipFile.new('test/data/generated/5entry.zip', %w[test/data/generated/longAscii.txt test/data/generated/empty.txt test/data/generated/empty_chmod640.txt test/data/generated/short.txt test/data/generated/longBinary.bin], - 'my zip comment') + TEST_ZIP2 = TestZipFile.new( + 'test/data/generated/5entry.zip', + %w[ + test/data/generated/longAscii.txt + test/data/generated/empty.txt + test/data/generated/empty_chmod640.txt + test/data/generated/short.txt + test/data/generated/longBinary.bin + ], + 'my zip comment' + ) TEST_ZIP3 = TestZipFile.new('test/data/generated/test1.zip', %w[test/data/file1.txt]) TEST_ZIP4 = TestZipFile.new('test/data/generated/zipWithDir.zip', ['test/data/file1.txt', TestFiles::EMPTY_TEST_DIR]) diff --git a/test/output_stream_test.rb b/test/output_stream_test.rb index 106ba6af..40205076 100644 --- a/test/output_stream_test.rb +++ b/test/output_stream_test.rb @@ -90,7 +90,8 @@ def test_put_next_entry_using_zip_entry_creates_entries_with_correct_timestamps ::Zip::InputStream.open(TEST_ZIP.zip_name) do |io| while (entry = io.get_next_entry) - assert(::Zip::DOSTime.at(file.mtime).dos_equals(::Zip::DOSTime.at(entry.mtime))) # Compare DOS Times, since they are stored with two seconds accuracy + # Compare DOS Times, since they are stored with two seconds accuracy + assert(::Zip::DOSTime.at(file.mtime).dos_equals(::Zip::DOSTime.at(entry.mtime))) end end end From 0e25e63d38723730136ffbee551a872990152c76 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 29 Sep 2019 19:49:16 +0100 Subject: [PATCH 131/469] Turn off Metrics/BlockLength for the tests. --- .rubocop.yml | 5 +++++ .rubocop_todo.yml | 7 ------- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 4fe77b94..99c0e84c 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -23,6 +23,11 @@ Lint/UselessComparison: Exclude: - 'test/entry_test.rb' +# Turn block length metrics off for the tests. +Metrics/BlockLength: + Exclude: + - 'test/**/*.rb' + # Rubocop confuses these as instances of "memoization". Naming/MemoizedInstanceVariableName: Exclude: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 8ed38aa7..d165d400 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -18,12 +18,6 @@ Layout/EmptyLinesAroundClassBody: Metrics/AbcSize: Max: 60 -# Offense count: 3 -# Configuration parameters: CountComments, ExcludedMethods. -# ExcludedMethods: refine -Metrics/BlockLength: - Max: 43 - # Offense count: 15 # Configuration parameters: CountComments. Metrics/ClassLength: @@ -210,7 +204,6 @@ Style/SafeNavigation: - 'test/filesystem/file_nonmutating_test.rb' - 'test/filesystem/file_stat_test.rb' - 'test/test_helper.rb' - # Offense count: 3 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, AllowSafeAssignment. From c97d560b691667ab2da6473702c2053ba885edce Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 29 Sep 2019 20:00:07 +0100 Subject: [PATCH 132/469] Configure Metrics/AbcSize and turn off for the tests. --- .rubocop.yml | 7 +++++++ .rubocop_todo.yml | 4 ---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 99c0e84c..e94a5a88 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -23,6 +23,13 @@ Lint/UselessComparison: Exclude: - 'test/entry_test.rb' +# Turn off ABC metrics for the tests and set a workable max given +# the current state of the code. +Metrics/AbcSize: + Max: 37 + Exclude: + - 'test/**/*.rb' + # Turn block length metrics off for the tests. Metrics/BlockLength: Exclude: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index d165d400..09e6b00a 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -14,10 +14,6 @@ Layout/EmptyLinesAroundClassBody: Exclude: - 'test/extra_field_ut_test.rb' -# Offense count: 120 -Metrics/AbcSize: - Max: 60 - # Offense count: 15 # Configuration parameters: CountComments. Metrics/ClassLength: From 5ce4e13ddd33443f01fa33c8208423b76d99fce3 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 30 Sep 2019 19:32:25 +0100 Subject: [PATCH 133/469] Configure and fix Style/ClassCheck cop. --- .rubocop.yml | 4 ++++ .rubocop_todo.yml | 14 -------------- lib/zip/entry.rb | 6 +++--- lib/zip/file.rb | 6 +++--- lib/zip/input_stream.rb | 2 +- lib/zip/output_stream.rb | 4 ++-- test/filesystem/file_nonmutating_test.rb | 2 +- 7 files changed, 14 insertions(+), 24 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index e94a5a88..5ae4e6f6 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -41,6 +41,10 @@ Naming/MemoizedInstanceVariableName: - 'lib/zip/extra_field/old_unix.rb' - 'lib/zip/extra_field/unix.rb' +# Set a consistent way of checking types. +Style/ClassCheck: + EnforcedStyle: kind_of? + # Allow this multi-line block chain as it actually reads better # than the alternatives. Style/MultilineBlockChain: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 09e6b00a..81a0b020 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -96,20 +96,6 @@ Style/ClassAndModuleChildren: - 'lib/zip/extra_field/zip64.rb' - 'lib/zip/extra_field/zip64_placeholder.rb' -# Offense count: 15 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: is_a?, kind_of? -Style/ClassCheck: - Exclude: - - 'lib/zip/central_directory.rb' - - 'lib/zip/entry_set.rb' - - 'lib/zip/file.rb' - - 'lib/zip/output_stream.rb' - - 'test/filesystem/file_nonmutating_test.rb' - - 'test/ioextras/fake_io_test.rb' - - 'test/output_stream_test.rb' - # Offense count: 26 Style/Documentation: Enabled: false diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index a3ad4997..a67c6568 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -70,7 +70,7 @@ def initialize(*args) @time = args[8] || ::Zip::DOSTime.now @ftype = name_is_directory? ? :directory : :file - @extra = ::Zip::ExtraField.new(@extra.to_s) unless @extra.is_a?(::Zip::ExtraField) + @extra = ::Zip::ExtraField.new(@extra.to_s) unless @extra.kind_of?(::Zip::ExtraField) end def encrypted? @@ -271,7 +271,7 @@ def read_local_entry(io) #:nodoc:all raise ::Zip::Error, 'Truncated local zip entry header' end - if @extra.is_a?(::Zip::ExtraField) + if @extra.kind_of?(::Zip::ExtraField) @extra.merge(extra) if extra else @extra = ::Zip::ExtraField.new(extra) @@ -380,7 +380,7 @@ def check_c_dir_entry_comment_size end def read_c_dir_extra_field(io) - if @extra.is_a?(::Zip::ExtraField) + if @extra.kind_of?(::Zip::ExtraField) @extra.merge(io.read(@extra_length)) else @extra = ::Zip::ExtraField.new(io.read(@extra_length)) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 2d274081..c768d07d 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -141,11 +141,11 @@ def add_buffer # (This can be used to extract data from a # downloaded zip archive without first saving it to disk.) def open_buffer(io, options = {}) - unless IO_METHODS.map { |method| io.respond_to?(method) }.all? || io.is_a?(String) + unless IO_METHODS.map { |method| io.respond_to?(method) }.all? || io.kind_of?(String) raise "Zip::File.open_buffer expects a String or IO-like argument (responds to #{IO_METHODS.join(', ')}). Found: #{io.class}" end - io = ::StringIO.new(io) if io.is_a?(::String) + io = ::StringIO.new(io) if io.kind_of?(::String) # https://github.com/rubyzip/rubyzip/issues/119 io.binmode if io.respond_to?(:binmode) @@ -344,7 +344,7 @@ def extract(entry, dest_path, &block) # Commits changes that has been made since the previous commit to # the zip archive. def commit - return if name.is_a?(StringIO) || !commit_required? + return if name.kind_of?(StringIO) || !commit_required? on_success_replace do |tmp_file| ::Zip::OutputStream.open(tmp_file) do |zos| diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index 8d86897c..f942d190 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -122,7 +122,7 @@ def get_io(io_or_file, offset = 0) def open_entry @current_entry = ::Zip::Entry.read_local_entry(@archive_io) - if @current_entry && @current_entry.encrypted? && @decrypter.is_a?(NullEncrypter) + if @current_entry && @current_entry.encrypted? && @decrypter.kind_of?(NullEncrypter) raise Error, 'password required to decode zip file' end diff --git a/lib/zip/output_stream.rb b/lib/zip/output_stream.rb index 978f4b01..2f628931 100644 --- a/lib/zip/output_stream.rb +++ b/lib/zip/output_stream.rb @@ -98,7 +98,7 @@ def put_next_entry(entry_name, comment = nil, extra = nil, compression_method = end new_entry.comment = comment unless comment.nil? unless extra.nil? - new_entry.extra = extra.is_a?(ExtraField) ? extra : ExtraField.new(extra.to_s) + new_entry.extra = extra.kind_of?(ExtraField) ? extra : ExtraField.new(extra.to_s) end new_entry.compression_method = compression_method unless compression_method.nil? init_next_entry(new_entry, level) @@ -108,7 +108,7 @@ def put_next_entry(entry_name, comment = nil, extra = nil, compression_method = def copy_raw_entry(entry) entry = entry.dup raise Error, 'zip stream is closed' if @closed - raise Error, 'entry is not a ZipEntry' unless entry.is_a?(Entry) + raise Error, 'entry is not a ZipEntry' unless entry.kind_of?(Entry) finalize_current_entry @entry_set << entry diff --git a/test/filesystem/file_nonmutating_test.rb b/test/filesystem/file_nonmutating_test.rb index af4db8f2..cfe18ade 100644 --- a/test/filesystem/file_nonmutating_test.rb +++ b/test/filesystem/file_nonmutating_test.rb @@ -441,7 +441,7 @@ def test_glob '*/foo/**/*.txt' => ['globTest/foo/bar/baz/foo.txt'] }.each do |spec, expected_results| results = zf.glob(spec) - assert(results.all? { |entry| entry.is_a? ::Zip::Entry }) + assert(results.all? { |entry| entry.kind_of? ::Zip::Entry }) result_strings = results.map(&:to_s) missing_matches = expected_results - result_strings From a9adfa26d6f18f891673e4266826b2ea4acc489a Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 9 Feb 2020 20:08:53 +0000 Subject: [PATCH 134/469] Configure Metrics/ClassLength and turn off for the tests. --- .rubocop.yml | 5 +++++ .rubocop_todo.yml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.rubocop.yml b/.rubocop.yml index 5ae4e6f6..2849a925 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -35,6 +35,11 @@ Metrics/BlockLength: Exclude: - 'test/**/*.rb' +# Turn class length metrics off for the tests. +Metrics/ClassLength: + Exclude: + - 'test/**/*.rb' + # Rubocop confuses these as instances of "memoization". Naming/MemoizedInstanceVariableName: Exclude: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 81a0b020..a5bd0ee4 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -17,7 +17,7 @@ Layout/EmptyLinesAroundClassBody: # Offense count: 15 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 579 + Max: 580 # Offense count: 26 Metrics/CyclomaticComplexity: From a187ec0c3899e9f6fc5d03855a5e0801db5a5890 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 9 Feb 2020 21:54:10 +0000 Subject: [PATCH 135/469] Configure Metrics/MethodLength and turn off for the tests. --- .rubocop.yml | 5 +++++ .rubocop_todo.yml | 4 +--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 2849a925..a3ccce57 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -40,6 +40,11 @@ Metrics/ClassLength: Exclude: - 'test/**/*.rb' +# Turn method length metrics off for the tests. +Metrics/MethodLength: + Exclude: + - 'test/**/*.rb' + # Rubocop confuses these as instances of "memoization". Naming/MemoizedInstanceVariableName: Exclude: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index a5bd0ee4..c2e7565e 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -26,9 +26,7 @@ Metrics/CyclomaticComplexity: # Offense count: 120 # Configuration parameters: CountComments, ExcludedMethods. Metrics/MethodLength: - Max: 45 - Exclude: - - 'test/**/*.rb' + Max: 30 # Offense count: 2 # Configuration parameters: CountKeywordArgs. From 4b8f74042aef46b7271c66f1331e7143ca79f649 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 9 Feb 2020 22:09:00 +0000 Subject: [PATCH 136/469] Fix Layout/EmptyLinesAroundClassBody cop. --- .rubocop_todo.yml | 8 -------- test/extra_field_ut_test.rb | 1 - 2 files changed, 9 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index c2e7565e..47a86b10 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,14 +6,6 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines, beginning_only, ending_only -Layout/EmptyLinesAroundClassBody: - Exclude: - - 'test/extra_field_ut_test.rb' - # Offense count: 15 # Configuration parameters: CountComments. Metrics/ClassLength: diff --git a/test/extra_field_ut_test.rb b/test/extra_field_ut_test.rb index ad2ab7a6..6b854978 100644 --- a/test/extra_field_ut_test.rb +++ b/test/extra_field_ut_test.rb @@ -1,7 +1,6 @@ require 'test_helper' class ZipExtraFieldUTTest < MiniTest::Test - PARSE_TESTS = [ ["UT\x05\x00\x01PS>A", 0b001, true, true, false], ["UT\x05\x00\x02PS>A", 0b010, false, true, true], From fae95e3c2984ce7ef5863627cca2cac1ddb4f420 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 9 Feb 2020 22:38:18 +0000 Subject: [PATCH 137/469] Fix Style/NumericLiteralPrefix cop. --- .rubocop_todo.yml | 8 -------- test/file_options_test.rb | 4 ++-- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 47a86b10..1b7af436 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -130,14 +130,6 @@ Style/ModuleFunction: Style/MutableConstant: Enabled: false -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedOctalStyle. -# SupportedOctalStyles: zero_with_o, zero_only -Style/NumericLiteralPrefix: - Exclude: - - 'test/file_options_test.rb' - # Offense count: 2 # Cop supports --auto-correct. # Configuration parameters: Strict. diff --git a/test/file_options_test.rb b/test/file_options_test.rb index 1a73e980..61b86e85 100644 --- a/test/file_options_test.rb +++ b/test/file_options_test.rb @@ -24,9 +24,9 @@ def teardown def test_restore_permissions # Copy and set up files with different permissions. ::FileUtils.cp(TXTPATH, TXTPATH_600) - ::File.chmod(0600, TXTPATH_600) + ::File.chmod(0o600, TXTPATH_600) ::FileUtils.cp(TXTPATH, TXTPATH_755) - ::File.chmod(0755, TXTPATH_755) + ::File.chmod(0o755, TXTPATH_755) ::Zip::File.open(ZIPPATH, true) do |zip| zip.add(ENTRY_1, TXTPATH) From d07b36b2e63b2b51feb0a935fe7e7c4bb169c1c3 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 9 Feb 2020 22:44:08 +0000 Subject: [PATCH 138/469] Fix Style/TernaryParentheses cop. --- .rubocop_todo.yml | 9 --------- lib/zip/crypto/decrypted_io.rb | 2 +- lib/zip/inflater.rb | 2 +- lib/zip/pass_thru_decompressor.rb | 2 +- 4 files changed, 3 insertions(+), 12 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 1b7af436..283ebbde 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -168,15 +168,6 @@ Style/SafeNavigation: - 'test/filesystem/file_nonmutating_test.rb' - 'test/filesystem/file_stat_test.rb' - 'test/test_helper.rb' -# Offense count: 3 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, AllowSafeAssignment. -# SupportedStyles: require_parentheses, require_no_parentheses, require_parentheses_when_complex -Style/TernaryParentheses: - Exclude: - - 'lib/zip/crypto/decrypted_io.rb' - - 'lib/zip/inflater.rb' - - 'lib/zip/pass_thru_decompressor.rb' # Offense count: 1 # Cop supports --auto-correct. diff --git a/lib/zip/crypto/decrypted_io.rb b/lib/zip/crypto/decrypted_io.rb index 8d8191f0..61a377da 100644 --- a/lib/zip/crypto/decrypted_io.rb +++ b/lib/zip/crypto/decrypted_io.rb @@ -8,7 +8,7 @@ def initialize(io, decrypter) end def read(length = nil, outbuf = +'') - return ((length.nil? || length.zero?) ? '' : nil) if eof + return (length.nil? || length.zero? ? '' : nil) if eof while length.nil? || (buffer.bytesize < length) break if input_finished? diff --git a/lib/zip/inflater.rb b/lib/zip/inflater.rb index 609ccc79..530f98aa 100644 --- a/lib/zip/inflater.rb +++ b/lib/zip/inflater.rb @@ -8,7 +8,7 @@ def initialize(*args) end def read(length = nil, outbuf = '') - return ((length.nil? || length.zero?) ? '' : nil) if eof + return (length.nil? || length.zero? ? '' : nil) if eof while length.nil? || (@buffer.bytesize < length) break if input_finished? diff --git a/lib/zip/pass_thru_decompressor.rb b/lib/zip/pass_thru_decompressor.rb index d7892ce4..e638540e 100644 --- a/lib/zip/pass_thru_decompressor.rb +++ b/lib/zip/pass_thru_decompressor.rb @@ -6,7 +6,7 @@ def initialize(*args) end def read(length = nil, outbuf = '') - return ((length.nil? || length.zero?) ? '' : nil) if eof + return (length.nil? || length.zero? ? '' : nil) if eof if length.nil? || (@read_so_far + length) > decompressed_size length = decompressed_size - @read_so_far From 989a565340c8cd60f80f59705d41af869ab02acf Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 9 Feb 2020 22:45:54 +0000 Subject: [PATCH 139/469] Fix Style/TrailingCommaInHashLiteral cop. --- .rubocop_todo.yml | 8 -------- lib/zip/constants.rb | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 283ebbde..99681397 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -168,11 +168,3 @@ Style/SafeNavigation: - 'test/filesystem/file_nonmutating_test.rb' - 'test/filesystem/file_stat_test.rb' - 'test/test_helper.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyleForMultiline. -# SupportedStylesForMultiline: comma, consistent_comma, no_comma -Style/TrailingCommaInHashLiteral: - Exclude: - - 'lib/zip/constants.rb' diff --git a/lib/zip/constants.rb b/lib/zip/constants.rb index 428c5126..fe89847a 100644 --- a/lib/zip/constants.rb +++ b/lib/zip/constants.rb @@ -110,6 +110,6 @@ module Zip COMPRESSION_METHOD_JPEG => 'JPEG variant', COMPRESSION_METHOD_WAVPACK => 'WavPack compressed data', COMPRESSION_METHOD_PPMD => 'PPMd version I, Rev 1', - COMPRESSION_METHOD_AES => 'AES encryption', + COMPRESSION_METHOD_AES => 'AES encryption' }.freeze end From c31ab81cf62b37c414ce54ea0ee1c6aadceb245a Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 9 Feb 2020 22:49:06 +0000 Subject: [PATCH 140/469] Fix Style/NumericLiterals cop. --- .rubocop_todo.yml | 6 ------ test/stored_support_test.rb | 8 ++++---- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 99681397..4bff4fa4 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -130,12 +130,6 @@ Style/ModuleFunction: Style/MutableConstant: Enabled: false -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: Strict. -Style/NumericLiterals: - MinDigits: 6 - # Offense count: 23 # Cop supports --auto-correct. # Configuration parameters: AutoCorrect, EnforcedStyle, IgnoredMethods. diff --git a/test/stored_support_test.rb b/test/stored_support_test.rb index e0d3ae0e..28836b9e 100644 --- a/test/stored_support_test.rb +++ b/test/stored_support_test.rb @@ -10,11 +10,11 @@ def test_read Zip::InputStream.open(STORED_ZIP_TEST_FILE) do |zis| entry = zis.get_next_entry assert_equal 'file1.txt', entry.name - assert_equal 1327, entry.size + assert_equal 1_327, entry.size assert_equal ::File.open(INPUT_FILE1, 'r').read, zis.read entry = zis.get_next_entry assert_equal 'file2.txt', entry.name - assert_equal 41234, entry.size + assert_equal 41_234, entry.size assert_equal ::File.open(INPUT_FILE2, 'r').read, zis.read end end @@ -23,11 +23,11 @@ def test_encrypted_read Zip::InputStream.open(ENCRYPTED_STORED_ZIP_TEST_FILE, 0, Zip::TraditionalDecrypter.new('password')) do |zis| entry = zis.get_next_entry assert_equal 'file1.txt', entry.name - assert_equal 1327, entry.size + assert_equal 1_327, entry.size assert_equal ::File.open(INPUT_FILE1, 'r').read, zis.read entry = zis.get_next_entry assert_equal 'file2.txt', entry.name - assert_equal 41234, entry.size + assert_equal 41_234, entry.size assert_equal ::File.open(INPUT_FILE2, 'r').read, zis.read end end From 87a63e0cc3de00e0ad01c82b7f7086671f9707f1 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 15 Feb 2020 16:12:57 +0000 Subject: [PATCH 141/469] Set TargetRubyVersion to match that in the gemspec. Currently 2.4 is the earliest supported ruby version. --- .rubocop.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.rubocop.yml b/.rubocop.yml index a3ccce57..27712bd8 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,5 +1,10 @@ inherit_from: .rubocop_todo.yml +# Set this to the minimum supported ruby in the gemspec. Otherwise +# we get errors if our ruby version doesn't match. +AllCops: + TargetRubyVersion: 2.4 + Layout/HashAlignment: EnforcedHashRocketStyle: table EnforcedColonStyle: table From 8d91d001fd214777f7a5a61ed2a3b919c33f4c8c Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Sun, 16 Feb 2020 08:14:43 +0000 Subject: [PATCH 142/469] Update changelog for #420 and #437 --- Changelog.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Changelog.md b/Changelog.md index a42b8320..6ec9a705 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,10 @@ # X.X.X (Next) - Fix frozen string literal error [#431](https://github.com/rubyzip/rubyzip/pull/431) +- Upgrade rubocop and fix various linting complaints [#437](https://github.com/rubyzip/rubyzip/pull/437) + +Tooling: +- Add a `bin/console` script for development [#420](https://github.com/rubyzip/rubyzip/pull/420) # 2.2.0 (2020-02-01) From 846e704048c443414a9f78934e07da005a63194c Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 17 Feb 2020 22:35:08 +0000 Subject: [PATCH 143/469] Fix Naming/BlockParameterName cop. --- .rubocop_todo.yml | 13 ------------- lib/zip/file.rb | 4 ++-- lib/zip/filesystem.rb | 24 ++++++++++++------------ samples/zipfind.rb | 18 +++++++++--------- test/central_directory_test.rb | 12 ++++++------ test/file_extract_directory_test.rb | 4 ++-- test/file_extract_test.rb | 4 ++-- test/output_stream_test.rb | 6 +++--- test/test_helper.rb | 4 ++-- 9 files changed, 38 insertions(+), 51 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 4bff4fa4..6c8d32ee 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -38,19 +38,6 @@ Naming/AccessorMethodName: - 'lib/zip/streamable_stream.rb' - 'test/file_permissions_test.rb' -# Offense count: 18 -# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. -Naming/BlockParameterName: - Exclude: - - 'lib/zip/file.rb' - - 'lib/zip/filesystem.rb' - - 'samples/zipfind.rb' - - 'test/central_directory_test.rb' - - 'test/file_extract_directory_test.rb' - - 'test/file_extract_test.rb' - - 'test/output_stream_test.rb' - - 'test/test_helper.rb' - # Offense count: 140 # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. # AllowedNames: io, id, to, by, on, in, at, ip, db, os diff --git a/lib/zip/file.rb b/lib/zip/file.rb index c768d07d..a677e3cf 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -169,8 +169,8 @@ def open_buffer(io, options = {}) # local entry headers (which contain the same information as the # central directory). def foreach(aZipFileName, &block) - ::Zip::File.open(aZipFileName) do |zipFile| - zipFile.each(&block) + ::Zip::File.open(aZipFileName) do |zip_file| + zip_file.each(&block) end end diff --git a/lib/zip/filesystem.rb b/lib/zip/filesystem.rb index 4f3e9954..50b8e168 100644 --- a/lib/zip/filesystem.rb +++ b/lib/zip/filesystem.rb @@ -265,8 +265,8 @@ def size?(fileName) end def chown(ownerInt, groupInt, *filenames) - filenames.each do |fileName| - e = get_entry(fileName) + filenames.each do |filename| + e = get_entry(filename) e.extra.create('IUnix') unless e.extra.member?('IUnix') e.extra['IUnix'].uid = ownerInt e.extra['IUnix'].gid = groupInt @@ -275,8 +275,8 @@ def chown(ownerInt, groupInt, *filenames) end def chmod(modeInt, *filenames) - filenames.each do |fileName| - e = get_entry(fileName) + filenames.each do |filename| + e = get_entry(filename) e.fstype = 3 # force convertion filesystem type to unix e.unix_perms = modeInt e.external_file_attributes = modeInt << 16 @@ -314,8 +314,8 @@ def join(*fragments) end def utime(modifiedTime, *fileNames) - fileNames.each do |fileName| - get_entry(fileName).time = modifiedTime + fileNames.each do |filename| + get_entry(filename).time = modifiedTime end end @@ -406,12 +406,12 @@ def foreach(fileName, aSep = $INPUT_RECORD_SEPARATOR, &aProc) end def delete(*args) - args.each do |fileName| - if directory?(fileName) - raise Errno::EISDIR, "Is a directory - \"#{fileName}\"" + args.each do |filename| + if directory?(filename) + raise Errno::EISDIR, "Is a directory - \"#{filename}\"" end - @mappedZip.remove(fileName) + @mappedZip.remove(filename) end end @@ -488,8 +488,8 @@ def foreach(aDirectoryName) path << '/' unless path.end_with?('/') path = Regexp.escape(path) subDirEntriesRegex = Regexp.new("^#{path}([^/]+)$") - @mappedZip.each do |fileName| - match = subDirEntriesRegex.match(fileName) + @mappedZip.each do |filename| + match = subDirEntriesRegex.match(filename) yield(match[1]) unless match.nil? end end diff --git a/samples/zipfind.rb b/samples/zipfind.rb index a88bc42d..1524f4fa 100755 --- a/samples/zipfind.rb +++ b/samples/zipfind.rb @@ -10,13 +10,13 @@ module Zip module ZipFind def self.find(path, zipFilePattern = /\.zip$/i) - Find.find(path) do |fileName| - yield(fileName) - next unless zipFilePattern.match(fileName) && File.file?(fileName) + Find.find(path) do |filename| + yield(filename) + next unless zipFilePattern.match(filename) && File.file?(filename) begin - Zip::File.foreach(fileName) do |zipEntry| - yield(fileName + File::SEPARATOR + zipEntry.to_s) + Zip::File.foreach(filename) do |entry| + yield(filename + File::SEPARATOR + entry.to_s) end rescue Errno::EACCES => e puts e @@ -25,8 +25,8 @@ def self.find(path, zipFilePattern = /\.zip$/i) end def self.find_file(path, fileNamePattern, zipFilePattern = /\.zip$/i) - find(path, zipFilePattern) do |fileName| - yield(fileName) if fileNamePattern.match(fileName) + find(path, zipFilePattern) do |filename| + yield(filename) if fileNamePattern.match(filename) end end end @@ -42,8 +42,8 @@ def self.run(args) check_args(args) Zip::ZipFind.find_file(args[PATH_ARG_INDEX], args[FILENAME_PATTERN_ARG_INDEX], - args[ZIPFILE_PATTERN_ARG_INDEX]) do |fileName| - report_entry_found fileName + args[ZIPFILE_PATTERN_ARG_INDEX]) do |filename| + report_entry_found filename end end diff --git a/test/central_directory_test.rb b/test/central_directory_test.rb index 28e12f4a..9f75a299 100644 --- a/test/central_directory_test.rb +++ b/test/central_directory_test.rb @@ -6,21 +6,21 @@ def teardown end def test_read_from_stream - ::File.open(TestZipFile::TEST_ZIP2.zip_name, 'rb') do |zipFile| - cdir = ::Zip::CentralDirectory.read_from_stream(zipFile) + ::File.open(TestZipFile::TEST_ZIP2.zip_name, 'rb') do |zip_file| + cdir = ::Zip::CentralDirectory.read_from_stream(zip_file) assert_equal(TestZipFile::TEST_ZIP2.entry_names.size, cdir.size) - assert(cdir.entries.sort.compare_enumerables(TestZipFile::TEST_ZIP2.entry_names.sort) do |cdirEntry, testEntryName| - cdirEntry.name == testEntryName + assert(cdir.entries.sort.compare_enumerables(TestZipFile::TEST_ZIP2.entry_names.sort) do |cdir_entry, test_entry_name| + cdir_entry.name == test_entry_name end) assert_equal(TestZipFile::TEST_ZIP2.comment, cdir.comment) end end def test_read_from_invalid_stream - File.open('test/data/file2.txt', 'rb') do |zipFile| + File.open('test/data/file2.txt', 'rb') do |zip_file| cdir = ::Zip::CentralDirectory.new - cdir.read_from_stream(zipFile) + cdir.read_from_stream(zip_file) end raise 'ZipError expected!' rescue ::Zip::Error diff --git a/test/file_extract_directory_test.rb b/test/file_extract_directory_test.rb index f14f7870..099835e1 100644 --- a/test/file_extract_directory_test.rb +++ b/test/file_extract_directory_test.rb @@ -42,9 +42,9 @@ def test_extract_directory_exists_as_file def test_extract_directory_exists_as_file_overwrite File.open(TEST_OUT_NAME, 'w') { |f| f.puts 'something' } gotCalled = false - extract_test_dir do |entry, destPath| + extract_test_dir do |entry, dest_path| gotCalled = true - assert_equal(TEST_OUT_NAME, destPath) + assert_equal(TEST_OUT_NAME, dest_path) assert(entry.directory?) true end diff --git a/test/file_extract_test.rb b/test/file_extract_test.rb index 3992a1ad..e166debe 100644 --- a/test/file_extract_test.rb +++ b/test/file_extract_test.rb @@ -53,9 +53,9 @@ def test_extract_exists_overwrite gotCalledCorrectly = false ::Zip::File.open(TEST_ZIP.zip_name) do |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) do |entry, extractLoc| + zf.extract(zf.entries.first, EXTRACTED_FILENAME) do |entry, extract_loc| gotCalledCorrectly = zf.entries.first == entry && - extractLoc == EXTRACTED_FILENAME + extract_loc == EXTRACTED_FILENAME true end end diff --git a/test/output_stream_test.rb b/test/output_stream_test.rb index 40205076..ab8a1ba6 100644 --- a/test/output_stream_test.rb +++ b/test/output_stream_test.rb @@ -121,9 +121,9 @@ def assert_i_o_error_in_closed_stream end def write_test_zip(zos) - TEST_ZIP.entry_names.each do |entryName| - zos.put_next_entry(entryName) - File.open(entryName, 'rb') { |f| zos.write(f.read) } + TEST_ZIP.entry_names.each do |entry_name| + zos.put_next_entry(entry_name) + File.open(entry_name, 'rb') { |f| zos.write(f.read) } end end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 0e14b6da..2b32dcb6 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -119,8 +119,8 @@ def self.assert_contents(filename, aString) def assert_stream_contents(zis, testZipFile) assert(!zis.nil?) - testZipFile.entry_names.each do |entryName| - assert_next_entry(entryName, zis) + testZipFile.entry_names.each do |entry_name| + assert_next_entry(entry_name, zis) end assert_nil(zis.get_next_entry) end From b0ee2683b0e89e46f73f589057562e2f2d6e29d7 Mon Sep 17 00:00:00 2001 From: Sebastian Henke Date: Wed, 19 Feb 2020 18:48:13 +0100 Subject: [PATCH 144/469] Set buffers to binmode by default --- lib/zip/file.rb | 1 + lib/zip/output_stream.rb | 1 + test/output_stream_test.rb | 11 +++++++++++ 3 files changed, 13 insertions(+) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index c768d07d..5aafe2e1 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -362,6 +362,7 @@ def commit # Write buffer write changes to buffer and return def write_buffer(io = ::StringIO.new('')) + io.binmode if io.respond_to?(:binmode) ::Zip::OutputStream.write_buffer(io) do |zos| @entry_set.each { |e| e.write_to_zip_output_stream(zos) } zos.comment = comment diff --git a/lib/zip/output_stream.rb b/lib/zip/output_stream.rb index 2f628931..266083cd 100644 --- a/lib/zip/output_stream.rb +++ b/lib/zip/output_stream.rb @@ -58,6 +58,7 @@ def open(file_name, encrypter = nil) # Same as #open but writes to a filestream instead def write_buffer(io = ::StringIO.new(''), encrypter = nil) + io.binmode if io.respond_to?(:binmode) zos = new(io, true, encrypter) yield zos zos.close_buffer diff --git a/test/output_stream_test.rb b/test/output_stream_test.rb index 40205076..e25fb84a 100644 --- a/test/output_stream_test.rb +++ b/test/output_stream_test.rb @@ -6,6 +6,8 @@ class ZipOutputStreamTest < MiniTest::Test TEST_ZIP = TestZipFile::TEST_ZIP2.clone TEST_ZIP.zip_name = 'test/data/generated/output.zip' + ASCII8BIT = 'ASCII-8BIT' + def test_new zos = ::Zip::OutputStream.new(TEST_ZIP.zip_name) zos.comment = TEST_ZIP.comment @@ -32,6 +34,15 @@ def test_write_buffer assert_test_zip_contents(TEST_ZIP) end + def test_write_buffer_binmode + io = ::StringIO.new('') + buffer = ::Zip::OutputStream.write_buffer(io) do |zos| + zos.comment = TEST_ZIP.comment + write_test_zip(zos) + end + assert buffer.external_encoding.name === ASCII8BIT + end + def test_write_buffer_with_temp_file tmp_file = Tempfile.new('') From fcadea61e2f9dd0aadf9c83c0b817d01a8ed8ee6 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 17 Feb 2020 22:51:53 +0000 Subject: [PATCH 145/469] Fix Naming/MethodParameterName cop in the samples. --- .rubocop_todo.yml | 4 +++- samples/qtzip.rb | 8 ++++---- samples/zipfind.rb | 14 +++++++------- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 6c8d32ee..1043278a 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -42,7 +42,9 @@ Naming/AccessorMethodName: # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. # AllowedNames: io, id, to, by, on, in, at, ip, db, os Naming/MethodParameterName: - Enabled: false + Exclude: + - 'lib/**/*.rb' + - 'test/**/*.rb' # Offense count: 721 # Configuration parameters: EnforcedStyle. diff --git a/samples/qtzip.rb b/samples/qtzip.rb index f76f57a7..2c189ed6 100755 --- a/samples/qtzip.rb +++ b/samples/qtzip.rb @@ -20,12 +20,12 @@ def initialize self, SLOT('extract_files()')) end - def zipfile(&proc) - Zip::File.open(@zip_filename, &proc) + def zipfile(&a_proc) + Zip::File.open(@zip_filename, &a_proc) end - def each(&proc) - Zip::File.foreach(@zip_filename, &proc) + def each(&a_proc) + Zip::File.foreach(@zip_filename, &a_proc) end def refresh diff --git a/samples/zipfind.rb b/samples/zipfind.rb index 1524f4fa..8f0dbf2e 100755 --- a/samples/zipfind.rb +++ b/samples/zipfind.rb @@ -9,10 +9,10 @@ module Zip module ZipFind - def self.find(path, zipFilePattern = /\.zip$/i) + def self.find(path, zip_file_pattern = /\.zip$/i) Find.find(path) do |filename| yield(filename) - next unless zipFilePattern.match(filename) && File.file?(filename) + next unless zip_file_pattern.match(filename) && File.file?(filename) begin Zip::File.foreach(filename) do |entry| @@ -24,9 +24,9 @@ def self.find(path, zipFilePattern = /\.zip$/i) end end - def self.find_file(path, fileNamePattern, zipFilePattern = /\.zip$/i) - find(path, zipFilePattern) do |filename| - yield(filename) if fileNamePattern.match(filename) + def self.find_file(path, filename_pattern, zip_file_pattern = /\.zip$/i) + find(path, zip_file_pattern) do |filename| + yield(filename) if filename_pattern.match(filename) end end end @@ -58,8 +58,8 @@ def self.usage puts "Usage: #{$PROGRAM_NAME} PATH ZIPFILENAME_PATTERN FILNAME_PATTERN" end - def self.report_entry_found(fileName) - puts fileName + def self.report_entry_found(filename) + puts filename end end From b09f05d8d325791ca7a8b1c1a52eb9a76f26db67 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 17 Feb 2020 23:13:46 +0000 Subject: [PATCH 146/469] Fix Naming/MethodParameterName cop in the tests. --- .rubocop_todo.yml | 1 - test/case_sensitivity_test.rb | 9 ++-- test/deflater_test.rb | 12 ++--- test/file_extract_directory_test.rb | 10 ++-- test/file_test.rb | 20 +++++--- test/ioextras/abstract_input_stream_test.rb | 10 ++-- test/local_entry_test.rb | 12 ++--- test/settings_test.rb | 19 ++++--- test/test_helper.rb | 56 ++++++++++----------- 9 files changed, 80 insertions(+), 69 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 1043278a..6a3c8530 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -44,7 +44,6 @@ Naming/AccessorMethodName: Naming/MethodParameterName: Exclude: - 'lib/**/*.rb' - - 'test/**/*.rb' # Offense count: 721 # Configuration parameters: EnforcedStyle. diff --git a/test/case_sensitivity_test.rb b/test/case_sensitivity_test.rb index 92fc0f6c..5966c4fa 100644 --- a/test/case_sensitivity_test.rb +++ b/test/case_sensitivity_test.rb @@ -63,8 +63,11 @@ def test_add_case_sensitive_read_case_insensitive private - def assert_contains(zf, entryName, filename = entryName) - refute_nil(zf.entries.detect { |e| e.name == entryName }, "entry #{entryName} not in #{zf.entries.join(', ')} in zip file #{zf}") - assert_entry_contents(zf, entryName, filename) if File.exist?(filename) + def assert_contains(zip_file, entry_name, filename = entry_name) + refute_nil( + zip_file.entries.detect { |e| e.name == entry_name }, + "entry #{entry_name} not in #{zip_file.entries.join(', ')} in zip file #{zip_file}" + ) + assert_entry_contents(zip_file, entry_name, filename) if File.exist?(filename) end end diff --git a/test/deflater_test.rb b/test/deflater_test.rb index 78c22dfc..b76dcc36 100644 --- a/test/deflater_test.rb +++ b/test/deflater_test.rb @@ -42,12 +42,12 @@ def test_data_error private - def load_file(fileName) - File.open(fileName, 'rb', &:read) + def load_file(filename) + File.open(filename, 'rb', &:read) end - def deflate(data, fileName) - File.open(fileName, 'wb') do |file| + def deflate(data, filename) + File.open(filename, 'wb') do |file| deflater = ::Zip::Deflater.new(file) deflater << data deflater.finish @@ -56,8 +56,8 @@ def deflate(data, fileName) end end - def inflate(fileName) - File.open(fileName, 'rb') do |file| + def inflate(filename) + File.open(filename, 'rb') do |file| inflater = ::Zip::Inflater.new(file) inflater.read end diff --git a/test/file_extract_directory_test.rb b/test/file_extract_directory_test.rb index 099835e1..8e5b7775 100644 --- a/test/file_extract_directory_test.rb +++ b/test/file_extract_directory_test.rb @@ -5,14 +5,14 @@ class ZipFileExtractDirectoryTest < MiniTest::Test TEST_OUT_NAME = 'test/data/generated/emptyOutDir' - def open_zip(&aProc) - assert(!aProc.nil?) - ::Zip::File.open(TestZipFile::TEST_ZIP4.zip_name, &aProc) + def open_zip(&a_proc) + assert(!a_proc.nil?) + ::Zip::File.open(TestZipFile::TEST_ZIP4.zip_name, &a_proc) end - def extract_test_dir(&aProc) + def extract_test_dir(&a_proc) open_zip do |zf| - zf.extract(TestFiles::EMPTY_TEST_DIR, TEST_OUT_NAME, &aProc) + zf.extract(TestFiles::EMPTY_TEST_DIR, TEST_OUT_NAME, &a_proc) end end diff --git a/test/file_test.rb b/test/file_test.rb index 21aa72f7..20fbf48f 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -680,12 +680,18 @@ def test_find_get_entry private - def assert_contains(zf, entryName, filename = entryName) - refute_nil(zf.entries.detect { |e| e.name == entryName }, "entry #{entryName} not in #{zf.entries.join(', ')} in zip file #{zf}") - assert_entry_contents(zf, entryName, filename) if File.exist?(filename) - end - - def assert_not_contains(zf, entryName) - assert_nil(zf.entries.detect { |e| e.name == entryName }, "entry #{entryName} in #{zf.entries.join(', ')} in zip file #{zf}") + def assert_contains(zip_file, entry_name, filename = entry_name) + refute_nil( + zip_file.entries.detect { |e| e.name == entry_name }, + "entry #{entry_name} not in #{zip_file.entries.join(', ')} in zip file #{zip_file}" + ) + assert_entry_contents(zip_file, entry_name, filename) if File.exist?(filename) + end + + def assert_not_contains(zip_file, entry_name) + assert_nil( + zip_file.entries.detect { |e| e.name == entry_name }, + "entry #{entry_name} in #{zip_file.entries.join(', ')} in zip file #{zip_file}" + ) end end diff --git a/test/ioextras/abstract_input_stream_test.rb b/test/ioextras/abstract_input_stream_test.rb index f8986b84..4be0ba8d 100644 --- a/test/ioextras/abstract_input_stream_test.rb +++ b/test/ioextras/abstract_input_stream_test.rb @@ -11,15 +11,15 @@ class AbstractInputStreamTest < MiniTest::Test class TestAbstractInputStream include ::Zip::IOExtras::AbstractInputStream - def initialize(aString) + def initialize(string) super() - @contents = aString + @contents = string @readPointer = 0 end - def sysread(charsToRead, _buf = nil) - retVal = @contents[@readPointer, charsToRead] - @readPointer += charsToRead + def sysread(chars_to_read, _buf = nil) + retVal = @contents[@readPointer, chars_to_read] + @readPointer += chars_to_read retVal end diff --git a/test/local_entry_test.rb b/test/local_entry_test.rb index b02deb67..ce634c89 100644 --- a/test/local_entry_test.rb +++ b/test/local_entry_test.rb @@ -139,16 +139,16 @@ def compare_c_dir_entry_headers(entry1, entry2) assert_equal(entry1.comment, entry2.comment) end - def write_to_file(localFileName, centralFileName, entry) - ::File.open(localFileName, 'wb') { |f| entry.write_local_entry(f) } - ::File.open(centralFileName, 'wb') { |f| entry.write_c_dir_entry(f) } + def write_to_file(local_filename, central_filename, entry) + ::File.open(local_filename, 'wb') { |f| entry.write_local_entry(f) } + ::File.open(central_filename, 'wb') { |f| entry.write_c_dir_entry(f) } end - def read_from_file(localFileName, centralFileName) + def read_from_file(local_filename, central_filename) localEntry = nil cdirEntry = nil - ::File.open(localFileName, 'rb') { |f| localEntry = ::Zip::Entry.read_local_entry(f) } - ::File.open(centralFileName, 'rb') { |f| cdirEntry = ::Zip::Entry.read_c_dir_entry(f) } + ::File.open(local_filename, 'rb') { |f| localEntry = ::Zip::Entry.read_local_entry(f) } + ::File.open(central_filename, 'rb') { |f| cdirEntry = ::Zip::Entry.read_c_dir_entry(f) } [localEntry, cdirEntry] end end diff --git a/test/settings_test.rb b/test/settings_test.rb index ab5aa223..b79dc0ce 100644 --- a/test/settings_test.rb +++ b/test/settings_test.rb @@ -17,14 +17,14 @@ def teardown ::Zip.reset! end - def open_zip(&aProc) - refute_nil(aProc) - ::Zip::File.open(TestZipFile::TEST_ZIP4.zip_name, &aProc) + def open_zip(&a_proc) + refute_nil(a_proc) + ::Zip::File.open(TestZipFile::TEST_ZIP4.zip_name, &a_proc) end - def extract_test_dir(&aProc) + def extract_test_dir(&a_proc) open_zip do |zf| - zf.extract(TestFiles::EMPTY_TEST_DIR, TEST_OUT_NAME, &aProc) + zf.extract(TestFiles::EMPTY_TEST_DIR, TEST_OUT_NAME, &a_proc) end end @@ -88,8 +88,11 @@ def test_true_warn_invalid_date private - def assert_contains(zf, entryName, filename = entryName) - refute_nil(zf.entries.detect { |e| e.name == entryName }, "entry #{entryName} not in #{zf.entries.join(', ')} in zip file #{zf}") - assert_entry_contents(zf, entryName, filename) if File.exist?(filename) + def assert_contains(zip_file, entry_name, filename = entry_name) + refute_nil( + zip_file.entries.detect { |e| e.name == entry_name }, + "entry #{entry_name} not in #{zip_file.entries.join(', ')} in zip file #{zip_file}" + ) + assert_entry_contents(zip_file, entry_name, filename) if File.exist?(filename) end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 2b32dcb6..12405456 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -82,18 +82,18 @@ def assert_next_entry(filename, zis) assert_entry(filename, zis, zis.get_next_entry.name) end - def assert_entry(filename, zis, entryName) - assert_equal(filename, entryName) - assert_entry_contents_for_stream(filename, zis, entryName) + def assert_entry(filename, zis, entry_name) + assert_equal(filename, entry_name) + assert_entry_contents_for_stream(filename, zis, entry_name) end - def assert_entry_contents_for_stream(filename, zis, entryName) + def assert_entry_contents_for_stream(filename, zis, entry_name) File.open(filename, 'rb') do |file| expected = file.read actual = zis.read if expected != actual if (expected && actual) && (expected.length > 400 || actual.length > 400) - zipEntryFilename = entryName + '.zipEntry' + zipEntryFilename = entry_name + '.zipEntry' File.open(zipEntryFilename, 'wb') { |entryfile| entryfile << actual } raise("File '#{filename}' is different from '#{zipEntryFilename}'") else @@ -103,37 +103,37 @@ def assert_entry_contents_for_stream(filename, zis, entryName) end end - def self.assert_contents(filename, aString) + def self.assert_contents(filename, string) fileContents = '' File.open(filename, 'rb') { |f| fileContents = f.read } - return unless fileContents != aString + return unless fileContents != string - if fileContents.length > 400 || aString.length > 400 + if fileContents.length > 400 || string.length > 400 stringFile = filename + '.other' - File.open(stringFile, 'wb') { |f| f << aString } + File.open(stringFile, 'wb') { |f| f << string } raise("File '#{filename}' is different from contents of string stored in '#{stringFile}'") else - assert_equal(fileContents, aString) + assert_equal(fileContents, string) end end - def assert_stream_contents(zis, testZipFile) + def assert_stream_contents(zis, zip_file) assert(!zis.nil?) - testZipFile.entry_names.each do |entry_name| + zip_file.entry_names.each do |entry_name| assert_next_entry(entry_name, zis) end assert_nil(zis.get_next_entry) end - def assert_test_zip_contents(testZipFile) - ::Zip::InputStream.open(testZipFile.zip_name) do |zis| - assert_stream_contents(zis, testZipFile) + def assert_test_zip_contents(zip_file) + ::Zip::InputStream.open(zip_file.zip_name) do |zis| + assert_stream_contents(zis, zip_file) end end - def assert_entry_contents(zipFile, entryName, filename = entryName.to_s) - zis = zipFile.get_input_stream(entryName) - assert_entry_contents_for_stream(filename, zis, entryName) + def assert_entry_contents(zip_file, entry_name, filename = entry_name.to_s) + zis = zip_file.get_input_stream(entry_name) + assert_entry_contents_for_stream(filename, zis, entry_name) ensure zis.close if zis end @@ -155,19 +155,19 @@ def <<(data) end end - def run_crc_test(compressorClass) + def run_crc_test(compressor_class) str = "Here's a nice little text to compute the crc for! Ho hum, it is nice nice nice nice indeed." fakeOut = TestOutputStream.new - deflater = compressorClass.new(fakeOut) + deflater = compressor_class.new(fakeOut) deflater << str assert_equal(0x919920fc, deflater.crc) end end module Enumerable - def compare_enumerables(otherEnumerable) - otherAsArray = otherEnumerable.to_a + def compare_enumerables(enumerable) + otherAsArray = enumerable.to_a each_with_index do |element, index| return false unless yield(element, otherAsArray[index]) end @@ -190,21 +190,21 @@ def setup end module ExtraAssertions - def assert_forwarded(anObject, method, retVal, *expectedArgs) + def assert_forwarded(object, method, ret_val, *expected_args) callArgs = nil setCallArgsProc = proc { |args| callArgs = args } - anObject.instance_eval <<-END_EVAL, __FILE__, __LINE__ + 1 + object.instance_eval <<-END_EVAL, __FILE__, __LINE__ + 1 alias #{method}_org #{method} def #{method}(*args) ObjectSpace._id2ref(#{setCallArgsProc.object_id}).call(args) - ObjectSpace._id2ref(#{retVal.object_id}) + ObjectSpace._id2ref(#{ret_val.object_id}) end END_EVAL - assert_equal(retVal, yield) # Invoke test - assert_equal(expectedArgs, callArgs) + assert_equal(ret_val, yield) # Invoke test + assert_equal(expected_args, callArgs) ensure - anObject.instance_eval <<-END_EVAL, __FILE__, __LINE__ + 1 + object.instance_eval <<-END_EVAL, __FILE__, __LINE__ + 1 undef #{method} alias #{method} #{method}_org END_EVAL From aa6ea05d456c71ad24effd8a850c0df368a153fe Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 19 Feb 2020 07:28:46 +0000 Subject: [PATCH 147/469] Fix Naming/MethodParameterName cop in the library code. --- .rubocop_todo.yml | 7 - lib/zip/central_directory.rb | 4 +- lib/zip/crypto/traditional_encryption.rb | 18 +- lib/zip/dos_time.rb | 14 +- lib/zip/extra_field.rb | 14 +- lib/zip/file.rb | 54 ++-- lib/zip/filesystem.rb | 331 ++++++++++++----------- lib/zip/pass_thru_compressor.rb | 4 +- lib/zip/streamable_directory.rb | 6 +- lib/zip/streamable_stream.rb | 6 +- 10 files changed, 228 insertions(+), 230 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 6a3c8530..e4f4e79b 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -38,13 +38,6 @@ Naming/AccessorMethodName: - 'lib/zip/streamable_stream.rb' - 'test/file_permissions_test.rb' -# Offense count: 140 -# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. -# AllowedNames: io, id, to, by, on, in, at, ip, db, os -Naming/MethodParameterName: - Exclude: - - 'lib/**/*.rb' - # Offense count: 721 # Configuration parameters: EnforcedStyle. # SupportedStyles: snake_case, camelCase diff --git a/lib/zip/central_directory.rb b/lib/zip/central_directory.rb index 496d668d..9975884c 100644 --- a/lib/zip/central_directory.rb +++ b/lib/zip/central_directory.rb @@ -181,8 +181,8 @@ def buf.read(count) end # For iterating over the entries. - def each(&proc) - @entry_set.each(&proc) + def each(&a_proc) + @entry_set.each(&a_proc) end # Returns the number of entries in the central directory (and diff --git a/lib/zip/crypto/traditional_encryption.rb b/lib/zip/crypto/traditional_encryption.rb index 91e6ce16..270e9efd 100644 --- a/lib/zip/crypto/traditional_encryption.rb +++ b/lib/zip/crypto/traditional_encryption.rb @@ -24,8 +24,8 @@ def reset_keys! end end - def update_keys(n) - @key0 = ~Zlib.crc32(n, ~@key0) + def update_keys(num) + @key0 = ~Zlib.crc32(num, ~@key0) @key1 = ((@key1 + (@key0 & 0xff)) * 134_775_813 + 1) & 0xffffffff @key2 = ~Zlib.crc32((@key1 >> 24).chr, ~@key2) end @@ -63,10 +63,10 @@ def reset! private - def encode(n) + def encode(num) t = decrypt_byte - update_keys(n.chr) - t ^ n + update_keys(num.chr) + t ^ num end end @@ -86,10 +86,10 @@ def reset!(header) private - def decode(n) - n ^= decrypt_byte - update_keys(n.chr) - n + def decode(num) + num ^= decrypt_byte + update_keys(num.chr) + num end end end diff --git a/lib/zip/dos_time.rb b/lib/zip/dos_time.rb index c912b773..1d77aa40 100644 --- a/lib/zip/dos_time.rb +++ b/lib/zip/dos_time.rb @@ -34,13 +34,13 @@ def self.from_time(time) local(time.year, time.month, time.day, time.hour, time.min, time.sec) end - def self.parse_binary_dos_format(binaryDosDate, binaryDosTime) - second = 2 * (0b11111 & binaryDosTime) - minute = (0b11111100000 & binaryDosTime) >> 5 - hour = (0b1111100000000000 & binaryDosTime) >> 11 - day = (0b11111 & binaryDosDate) - month = (0b111100000 & binaryDosDate) >> 5 - year = ((0b1111111000000000 & binaryDosDate) >> 9) + 1980 + def self.parse_binary_dos_format(bin_dos_date, bin_dos_time) + second = 2 * (0b11111 & bin_dos_time) + minute = (0b11111100000 & bin_dos_time) >> 5 + hour = (0b1111100000000000 & bin_dos_time) >> 11 + day = (0b11111 & bin_dos_date) + month = (0b111100000 & bin_dos_date) >> 5 + year = ((0b1111111000000000 & bin_dos_date) >> 9) + 1980 begin local(year, month, day, hour, minute, second) end diff --git a/lib/zip/extra_field.rb b/lib/zip/extra_field.rb index e4b00b66..aa3ef8a8 100644 --- a/lib/zip/extra_field.rb +++ b/lib/zip/extra_field.rb @@ -6,23 +6,23 @@ def initialize(binstr = nil) merge(binstr) if binstr end - def extra_field_type_exist(binstr, id, len, i) + def extra_field_type_exist(binstr, id, len, index) field_name = ID_MAP[id].name if member?(field_name) - self[field_name].merge(binstr[i, len + 4]) + self[field_name].merge(binstr[index, len + 4]) else - field_obj = ID_MAP[id].new(binstr[i, len + 4]) + field_obj = ID_MAP[id].new(binstr[index, len + 4]) self[field_name] = field_obj end end - def extra_field_type_unknown(binstr, len, i) + def extra_field_type_unknown(binstr, len, index) create_unknown_item unless self['Unknown'] - if !len || len + 4 > binstr[i..-1].bytesize - self['Unknown'] << binstr[i..-1] + if !len || len + 4 > binstr[index..-1].bytesize + self['Unknown'] << binstr[index..-1] return end - self['Unknown'] << binstr[i, len + 4] + self['Unknown'] << binstr[index, len + 4] end def create_unknown_item diff --git a/lib/zip/file.rb b/lib/zip/file.rb index a677e3cf..0e7d4a90 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -168,8 +168,8 @@ def open_buffer(io, options = {}) # whereas ZipInputStream jumps through the entire archive accessing the # local entry headers (which contain the same information as the # central directory). - def foreach(aZipFileName, &block) - ::Zip::File.open(aZipFileName) do |zip_file| + def foreach(zip_file_name, &block) + ::Zip::File.open(zip_file_name) do |zip_file| zip_file.each(&block) end end @@ -255,8 +255,8 @@ def split(zip_file_name, segment_size = MAX_SEGMENT_SIZE, delete_zip_file = true # Returns an input stream to the specified entry. If a block is passed # the stream object is passed to the block and the stream is automatically # closed afterwards just as with ruby's builtin File.open method. - def get_input_stream(entry, &aProc) - get_entry(entry).get_input_stream(&aProc) + def get_input_stream(entry, &a_proc) + get_entry(entry).get_input_stream(&a_proc) end # Returns an output stream to the specified entry. If entry is not an instance @@ -267,7 +267,7 @@ def get_input_stream(entry, &aProc) def get_output_stream(entry, permission_int = nil, comment = nil, extra = nil, compressed_size = nil, crc = nil, compression_method = nil, size = nil, time = nil, - &aProc) + &a_proc) new_entry = if entry.kind_of?(Entry) @@ -282,7 +282,7 @@ def get_output_stream(entry, permission_int = nil, comment = nil, new_entry.unix_perms = permission_int zip_streamable_entry = StreamableStream.new(new_entry) @entry_set << zip_streamable_entry - zip_streamable_entry.get_output_stream(&aProc) + zip_streamable_entry.get_output_stream(&a_proc) end # Returns the name of the zip archive @@ -326,12 +326,12 @@ def rename(entry, new_name, &continue_on_exists_proc) @entry_set << foundEntry end - # Replaces the specified entry with the contents of srcPath (from + # Replaces the specified entry with the contents of src_path (from # the file system). - def replace(entry, srcPath) - check_file(srcPath) + def replace(entry, src_path) + check_file(src_path) remove(entry) - add(entry, srcPath) + add(entry, src_path) end # Extracts entry to file dest_path. @@ -409,37 +409,37 @@ def get_entry(entry) end # Creates a directory - def mkdir(entryName, permissionInt = 0o755) - raise Errno::EEXIST, "File exists - #{entryName}" if find_entry(entryName) + def mkdir(entry_name, permission = 0o755) + raise Errno::EEXIST, "File exists - #{entry_name}" if find_entry(entry_name) - entryName = entryName.dup.to_s - entryName << '/' unless entryName.end_with?('/') - @entry_set << ::Zip::StreamableDirectory.new(@name, entryName, nil, permissionInt) + entry_name = entry_name.dup.to_s + entry_name << '/' unless entry_name.end_with?('/') + @entry_set << ::Zip::StreamableDirectory.new(@name, entry_name, nil, permission) end private - def directory?(newEntry, srcPath) - srcPathIsDirectory = ::File.directory?(srcPath) - if newEntry.directory? && !srcPathIsDirectory + def directory?(new_entry, src_path) + srcPathIsDirectory = ::File.directory?(src_path) + if new_entry.directory? && !srcPathIsDirectory raise ArgumentError, - "entry name '#{newEntry}' indicates directory entry, but " \ - "'#{srcPath}' is not a directory" - elsif !newEntry.directory? && srcPathIsDirectory - newEntry.name += '/' + "entry name '#{new_entry}' indicates directory entry, but " \ + "'#{src_path}' is not a directory" + elsif !new_entry.directory? && srcPathIsDirectory + new_entry.name += '/' end - newEntry.directory? && srcPathIsDirectory + new_entry.directory? && srcPathIsDirectory end - def check_entry_exists(entryName, continue_on_exists_proc, procedureName) + def check_entry_exists(entry_name, continue_on_exists_proc, proc_name) continue_on_exists_proc ||= proc { Zip.continue_on_exists_proc } - return unless @entry_set.include?(entryName) + return unless @entry_set.include?(entry_name) if continue_on_exists_proc.call - remove get_entry(entryName) + remove get_entry(entry_name) else raise ::Zip::EntryExistsError, - procedureName + " failed. Entry #{entryName} already exists" + proc_name + " failed. Entry #{entry_name} already exists" end end diff --git a/lib/zip/filesystem.rb b/lib/zip/filesystem.rb index 50b8e168..28ebad4d 100644 --- a/lib/zip/filesystem.rb +++ b/lib/zip/filesystem.rb @@ -35,25 +35,25 @@ module Zip module FileSystem def initialize # :nodoc: - mappedZip = ZipFileNameMapper.new(self) - @zipFsDir = ZipFsDir.new(mappedZip) - @zipFsFile = ZipFsFile.new(mappedZip) - @zipFsDir.file = @zipFsFile - @zipFsFile.dir = @zipFsDir + mapped_zip = ZipFileNameMapper.new(self) + @zip_fs_dir = ZipFsDir.new(mapped_zip) + @zip_fs_file = ZipFsFile.new(mapped_zip) + @zip_fs_dir.file = @zip_fs_file + @zip_fs_file.dir = @zip_fs_dir end # Returns a ZipFsDir which is much like ruby's builtin Dir (class) # object, except it works on the Zip::File on which this method is # invoked def dir - @zipFsDir + @zip_fs_dir end # Returns a ZipFsFile which is much like ruby's builtin File (class) # object, except it works on the Zip::File on which this method is # invoked def file - @zipFsFile + @zip_fs_file end # Instances of this class are normally accessed via the accessor @@ -72,20 +72,20 @@ def delegate_to_fs_file(*methods) methods.each do |method| class_eval <<-END_EVAL, __FILE__, __LINE__ + 1 def #{method} # def file? - @zipFsFile.#{method}(@entryName) # @zipFsFile.file?(@entryName) + @zip_fs_file.#{method}(@entry_name) # @zip_fs_file.file?(@entry_name) end # end END_EVAL end end end - def initialize(zipFsFile, entryName) - @zipFsFile = zipFsFile - @entryName = entryName + def initialize(zip_fs_file, entry_name) + @zip_fs_file = zip_fs_file + @entry_name = entry_name end - def kind_of?(t) - super || t == ::File::Stat + def kind_of?(type) + super || type == ::File::Stat end delegate_to_fs_file :file?, :directory?, :pipe?, :chardev?, :symlink?, @@ -98,7 +98,7 @@ def blocks end def get_entry - @zipFsFile.__send__(:get_entry, @entryName) + @zip_fs_file.__send__(:get_entry, @entry_name) end private :get_entry @@ -168,29 +168,29 @@ def mode end end - def initialize(mappedZip) - @mappedZip = mappedZip + def initialize(mapped_zip) + @mapped_zip = mapped_zip end - def get_entry(fileName) - unless exists?(fileName) - raise Errno::ENOENT, "No such file or directory - #{fileName}" + def get_entry(filename) + unless exists?(filename) + raise Errno::ENOENT, "No such file or directory - #{filename}" end - @mappedZip.find_entry(fileName) + @mapped_zip.find_entry(filename) end private :get_entry - def unix_mode_cmp(fileName, mode) - e = get_entry(fileName) + def unix_mode_cmp(filename, mode) + e = get_entry(filename) e.fstype == 3 && ((e.external_file_attributes >> 16) & mode) != 0 rescue Errno::ENOENT false end private :unix_mode_cmp - def exists?(fileName) - expand_path(fileName) == '/' || !@mappedZip.find_entry(fileName).nil? + def exists?(filename) + expand_path(filename) == '/' || !@mapped_zip.find_entry(filename).nil? end alias exist? exists? @@ -198,133 +198,133 @@ def exists?(fileName) alias owned? exists? alias grpowned? exists? - def readable?(fileName) - unix_mode_cmp(fileName, 0o444) + def readable?(filename) + unix_mode_cmp(filename, 0o444) end alias readable_real? readable? - def writable?(fileName) - unix_mode_cmp(fileName, 0o222) + def writable?(filename) + unix_mode_cmp(filename, 0o222) end alias writable_real? writable? - def executable?(fileName) - unix_mode_cmp(fileName, 0o111) + def executable?(filename) + unix_mode_cmp(filename, 0o111) end alias executable_real? executable? - def setuid?(fileName) - unix_mode_cmp(fileName, 0o4000) + def setuid?(filename) + unix_mode_cmp(filename, 0o4000) end - def setgid?(fileName) - unix_mode_cmp(fileName, 0o2000) + def setgid?(filename) + unix_mode_cmp(filename, 0o2000) end - def sticky?(fileName) - unix_mode_cmp(fileName, 0o1000) + def sticky?(filename) + unix_mode_cmp(filename, 0o1000) end def umask(*args) ::File.umask(*args) end - def truncate(_fileName, _len) + def truncate(_filename, _len) raise StandardError, 'truncate not supported' end - def directory?(fileName) - entry = @mappedZip.find_entry(fileName) - expand_path(fileName) == '/' || (!entry.nil? && entry.directory?) + def directory?(filename) + entry = @mapped_zip.find_entry(filename) + expand_path(filename) == '/' || (!entry.nil? && entry.directory?) end - def open(fileName, openMode = 'r', permissionInt = 0o644, &block) - openMode.delete!('b') # ignore b option - case openMode + def open(filename, mode = 'r', permissions = 0o644, &block) + mode.delete!('b') # ignore b option + case mode when 'r' - @mappedZip.get_input_stream(fileName, &block) + @mapped_zip.get_input_stream(filename, &block) when 'w' - @mappedZip.get_output_stream(fileName, permissionInt, &block) + @mapped_zip.get_output_stream(filename, permissions, &block) else - raise StandardError, "openmode '#{openMode} not supported" unless openMode == 'r' + raise StandardError, "openmode '#{mode} not supported" unless mode == 'r' end end - def new(fileName, openMode = 'r') - self.open(fileName, openMode) + def new(filename, mode = 'r') + self.open(filename, mode) end - def size(fileName) - @mappedZip.get_entry(fileName).size + def size(filename) + @mapped_zip.get_entry(filename).size end # Returns nil for not found and nil for directories - def size?(fileName) - entry = @mappedZip.find_entry(fileName) + def size?(filename) + entry = @mapped_zip.find_entry(filename) entry.nil? || entry.directory? ? nil : entry.size end - def chown(ownerInt, groupInt, *filenames) + def chown(owner, group, *filenames) filenames.each do |filename| e = get_entry(filename) e.extra.create('IUnix') unless e.extra.member?('IUnix') - e.extra['IUnix'].uid = ownerInt - e.extra['IUnix'].gid = groupInt + e.extra['IUnix'].uid = owner + e.extra['IUnix'].gid = group end filenames.size end - def chmod(modeInt, *filenames) + def chmod(mode, *filenames) filenames.each do |filename| e = get_entry(filename) e.fstype = 3 # force convertion filesystem type to unix - e.unix_perms = modeInt - e.external_file_attributes = modeInt << 16 + e.unix_perms = mode + e.external_file_attributes = mode << 16 e.dirty = true end filenames.size end - def zero?(fileName) - sz = size(fileName) + def zero?(filename) + sz = size(filename) sz.nil? || sz == 0 rescue Errno::ENOENT false end - def file?(fileName) - entry = @mappedZip.find_entry(fileName) + def file?(filename) + entry = @mapped_zip.find_entry(filename) !entry.nil? && entry.file? end - def dirname(fileName) - ::File.dirname(fileName) + def dirname(filename) + ::File.dirname(filename) end - def basename(fileName) - ::File.basename(fileName) + def basename(filename) + ::File.basename(filename) end - def split(fileName) - ::File.split(fileName) + def split(filename) + ::File.split(filename) end def join(*fragments) ::File.join(*fragments) end - def utime(modifiedTime, *fileNames) - fileNames.each do |filename| - get_entry(filename).time = modifiedTime + def utime(modified_time, *filenames) + filenames.each do |filename| + get_entry(filename).time = modified_time end end - def mtime(fileName) - @mappedZip.get_entry(fileName).mtime + def mtime(filename) + @mapped_zip.get_entry(filename).mtime end - def atime(fileName) - e = get_entry(fileName) + def atime(filename) + e = get_entry(filename) if e.extra.member? 'UniversalTime' e.extra['UniversalTime'].atime elsif e.extra.member? 'NTFS' @@ -332,8 +332,8 @@ def atime(fileName) end end - def ctime(fileName) - e = get_entry(fileName) + def ctime(filename) + e = get_entry(filename) if e.extra.member? 'UniversalTime' e.extra['UniversalTime'].ctime elsif e.extra.member? 'NTFS' @@ -353,27 +353,27 @@ def chardev?(_filename) false end - def symlink?(_fileName) + def symlink?(_filename) false end - def socket?(_fileName) + def socket?(_filename) false end - def ftype(fileName) - @mappedZip.get_entry(fileName).directory? ? 'directory' : 'file' + def ftype(filename) + @mapped_zip.get_entry(filename).directory? ? 'directory' : 'file' end - def readlink(_fileName) + def readlink(_filename) raise NotImplementedError, 'The readlink() function is not implemented' end - def symlink(_fileName, _symlinkName) + def symlink(_filename, _symlink_name) raise NotImplementedError, 'The symlink() function is not implemented' end - def link(_fileName, _symlinkName) + def link(_filename, _symlink_name) raise NotImplementedError, 'The link() function is not implemented' end @@ -381,28 +381,28 @@ def pipe raise NotImplementedError, 'The pipe() function is not implemented' end - def stat(fileName) - raise Errno::ENOENT, fileName unless exists?(fileName) + def stat(filename) + raise Errno::ENOENT, filename unless exists?(filename) - ZipFsStat.new(self, fileName) + ZipFsStat.new(self, filename) end alias lstat stat - def readlines(fileName) - self.open(fileName, &:readlines) + def readlines(filename) + self.open(filename, &:readlines) end - def read(fileName) - @mappedZip.read(fileName) + def read(filename) + @mapped_zip.read(filename) end - def popen(*args, &aProc) - ::File.popen(*args, &aProc) + def popen(*args, &a_proc) + ::File.popen(*args, &a_proc) end - def foreach(fileName, aSep = $INPUT_RECORD_SEPARATOR, &aProc) - self.open(fileName) { |is| is.each_line(aSep, &aProc) } + def foreach(filename, sep = $INPUT_RECORD_SEPARATOR, &a_proc) + self.open(filename) { |is| is.each_line(sep, &a_proc) } end def delete(*args) @@ -411,18 +411,18 @@ def delete(*args) raise Errno::EISDIR, "Is a directory - \"#{filename}\"" end - @mappedZip.remove(filename) + @mapped_zip.remove(filename) end end - def rename(fileToRename, newName) - @mappedZip.rename(fileToRename, newName) { true } + def rename(file_to_rename, new_name) + @mapped_zip.rename(file_to_rename, new_name) { true } end alias unlink delete - def expand_path(aPath) - @mappedZip.expand_path(aPath) + def expand_path(path) + @mapped_zip.expand_path(path) end end @@ -433,18 +433,18 @@ def expand_path(aPath) # The individual methods are not documented due to their # similarity with the methods in Dir class ZipFsDir - def initialize(mappedZip) - @mappedZip = mappedZip + def initialize(mapped_zip) + @mapped_zip = mapped_zip end attr_writer :file - def new(aDirectoryName) - ZipFsDirIterator.new(entries(aDirectoryName)) + def new(directory_name) + ZipFsDirIterator.new(entries(directory_name)) end - def open(aDirectoryName) - dirIt = new(aDirectoryName) + def open(directory_name) + dirIt = new(directory_name) if block_given? begin yield(dirIt) @@ -457,55 +457,55 @@ def open(aDirectoryName) end def pwd - @mappedZip.pwd + @mapped_zip.pwd end alias getwd pwd - def chdir(aDirectoryName) - unless @file.stat(aDirectoryName).directory? - raise Errno::EINVAL, "Invalid argument - #{aDirectoryName}" + def chdir(directory_name) + unless @file.stat(directory_name).directory? + raise Errno::EINVAL, "Invalid argument - #{directory_name}" end - @mappedZip.pwd = @file.expand_path(aDirectoryName) + @mapped_zip.pwd = @file.expand_path(directory_name) end - def entries(aDirectoryName) + def entries(directory_name) entries = [] - foreach(aDirectoryName) { |e| entries << e } + foreach(directory_name) { |e| entries << e } entries end def glob(*args, &block) - @mappedZip.glob(*args, &block) + @mapped_zip.glob(*args, &block) end - def foreach(aDirectoryName) - unless @file.stat(aDirectoryName).directory? - raise Errno::ENOTDIR, aDirectoryName + def foreach(directory_name) + unless @file.stat(directory_name).directory? + raise Errno::ENOTDIR, directory_name end - path = @file.expand_path(aDirectoryName) + path = @file.expand_path(directory_name) path << '/' unless path.end_with?('/') path = Regexp.escape(path) subDirEntriesRegex = Regexp.new("^#{path}([^/]+)$") - @mappedZip.each do |filename| + @mapped_zip.each do |filename| match = subDirEntriesRegex.match(filename) yield(match[1]) unless match.nil? end end - def delete(entryName) - unless @file.stat(entryName).directory? - raise Errno::EINVAL, "Invalid argument - #{entryName}" + def delete(entry_name) + unless @file.stat(entry_name).directory? + raise Errno::EINVAL, "Invalid argument - #{entry_name}" end - @mappedZip.remove(entryName) + @mapped_zip.remove(entry_name) end alias rmdir delete alias unlink delete - def mkdir(entryName, permissionInt = 0o755) - @mappedZip.mkdir(entryName, permissionInt) + def mkdir(entry_name, permissions = 0o755) + @mapped_zip.mkdir(entry_name, permissions) end def chroot(*_args) @@ -516,41 +516,41 @@ def chroot(*_args) class ZipFsDirIterator # :nodoc:all include Enumerable - def initialize(arrayOfFileNames) - @fileNames = arrayOfFileNames + def initialize(filenames) + @filenames = filenames @index = 0 end def close - @fileNames = nil + @filenames = nil end - def each(&aProc) - raise IOError, 'closed directory' if @fileNames.nil? + def each(&a_proc) + raise IOError, 'closed directory' if @filenames.nil? - @fileNames.each(&aProc) + @filenames.each(&a_proc) end def read - raise IOError, 'closed directory' if @fileNames.nil? + raise IOError, 'closed directory' if @filenames.nil? - @fileNames[(@index += 1) - 1] + @filenames[(@index += 1) - 1] end def rewind - raise IOError, 'closed directory' if @fileNames.nil? + raise IOError, 'closed directory' if @filenames.nil? @index = 0 end - def seek(anIntegerPosition) - raise IOError, 'closed directory' if @fileNames.nil? + def seek(position) + raise IOError, 'closed directory' if @filenames.nil? - @index = anIntegerPosition + @index = position end def tell - raise IOError, 'closed directory' if @fileNames.nil? + raise IOError, 'closed directory' if @filenames.nil? @index end @@ -561,60 +561,65 @@ def tell class ZipFileNameMapper # :nodoc:all include Enumerable - def initialize(zipFile) - @zipFile = zipFile + def initialize(zip_file) + @zip_file = zip_file @pwd = '/' end attr_accessor :pwd - def find_entry(fileName) - @zipFile.find_entry(expand_to_entry(fileName)) + def find_entry(filename) + @zip_file.find_entry(expand_to_entry(filename)) end - def get_entry(fileName) - @zipFile.get_entry(expand_to_entry(fileName)) + def get_entry(filename) + @zip_file.get_entry(expand_to_entry(filename)) end - def get_input_stream(fileName, &aProc) - @zipFile.get_input_stream(expand_to_entry(fileName), &aProc) + def get_input_stream(filename, &a_proc) + @zip_file.get_input_stream(expand_to_entry(filename), &a_proc) end - def get_output_stream(fileName, permissionInt = nil, &aProc) - @zipFile.get_output_stream(expand_to_entry(fileName), permissionInt, &aProc) + def get_output_stream(filename, permissions = nil, &a_proc) + @zip_file.get_output_stream( + expand_to_entry(filename), permissions, &a_proc + ) end def glob(pattern, *flags, &block) - @zipFile.glob(expand_to_entry(pattern), *flags, &block) + @zip_file.glob(expand_to_entry(pattern), *flags, &block) end - def read(fileName) - @zipFile.read(expand_to_entry(fileName)) + def read(filename) + @zip_file.read(expand_to_entry(filename)) end - def remove(fileName) - @zipFile.remove(expand_to_entry(fileName)) + def remove(filename) + @zip_file.remove(expand_to_entry(filename)) end - def rename(fileName, newName, &continueOnExistsProc) - @zipFile.rename(expand_to_entry(fileName), expand_to_entry(newName), - &continueOnExistsProc) + def rename(filename, new_name, &continue_on_exists_proc) + @zip_file.rename( + expand_to_entry(filename), + expand_to_entry(new_name), + &continue_on_exists_proc + ) end - def mkdir(fileName, permissionInt = 0o755) - @zipFile.mkdir(expand_to_entry(fileName), permissionInt) + def mkdir(filename, permissions = 0o755) + @zip_file.mkdir(expand_to_entry(filename), permissions) end # Turns entries into strings and adds leading / # and removes trailing slash on directories def each - @zipFile.each do |e| + @zip_file.each do |e| yield('/' + e.to_s.chomp('/')) end end - def expand_path(aPath) - expanded = aPath.start_with?('/') ? aPath : ::File.join(@pwd, aPath) + def expand_path(path) + expanded = path.start_with?('/') ? path : ::File.join(@pwd, path) expanded.gsub!(/\/\.(\/|$)/, '') expanded.gsub!(/[^\/]+\/\.\.(\/|$)/, '') expanded.empty? ? '/' : expanded @@ -622,8 +627,8 @@ def expand_path(aPath) private - def expand_to_entry(aPath) - expand_path(aPath)[1..-1] + def expand_to_entry(path) + expand_path(path)[1..-1] end end end diff --git a/lib/zip/pass_thru_compressor.rb b/lib/zip/pass_thru_compressor.rb index fdca2481..2dbaa273 100644 --- a/lib/zip/pass_thru_compressor.rb +++ b/lib/zip/pass_thru_compressor.rb @@ -1,8 +1,8 @@ module Zip class PassThruCompressor < Compressor #:nodoc:all - def initialize(outputStream) + def initialize(output_stream) super() - @output_stream = outputStream + @output_stream = output_stream @crc = Zlib.crc32 @size = 0 end diff --git a/lib/zip/streamable_directory.rb b/lib/zip/streamable_directory.rb index 4560663c..3738ce2c 100644 --- a/lib/zip/streamable_directory.rb +++ b/lib/zip/streamable_directory.rb @@ -1,11 +1,11 @@ module Zip class StreamableDirectory < Entry - def initialize(zipfile, entry, srcPath = nil, permissionInt = nil) + def initialize(zipfile, entry, src_path = nil, permission = nil) super(zipfile, entry) @ftype = :directory - entry.get_extra_attributes_from_path(srcPath) if srcPath - @unix_perms = permissionInt if permissionInt + entry.get_extra_attributes_from_path(src_path) if src_path + @unix_perms = permission if permission end end end diff --git a/lib/zip/streamable_stream.rb b/lib/zip/streamable_stream.rb index 1a726b99..68f3e0e8 100644 --- a/lib/zip/streamable_stream.rb +++ b/lib/zip/streamable_stream.rb @@ -36,9 +36,9 @@ def get_input_stream end end - def write_to_zip_output_stream(aZipOutputStream) - aZipOutputStream.put_next_entry(self) - get_input_stream { |is| ::Zip::IOExtras.copy_stream(aZipOutputStream, is) } + def write_to_zip_output_stream(output_stream) + output_stream.put_next_entry(self) + get_input_stream { |is| ::Zip::IOExtras.copy_stream(output_stream, is) } end def clean_up From 7626423994bf8a8eaaf2421132aaa6d8e94559d7 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 19 Feb 2020 07:37:32 +0000 Subject: [PATCH 148/469] Fix Naming/VariableName cop in the samples. --- .rubocop_todo.yml | 4 +++- samples/gtk_ruby_zip.rb | 36 ++++++++++++++++++------------------ 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index e4f4e79b..1d169a87 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -42,7 +42,9 @@ Naming/AccessorMethodName: # Configuration parameters: EnforcedStyle. # SupportedStyles: snake_case, camelCase Naming/VariableName: - Enabled: false + Exclude: + - 'lib/**/*.rb' + - 'test/**/*.rb' # Offense count: 7 # Configuration parameters: EnforcedStyle. diff --git a/samples/gtk_ruby_zip.rb b/samples/gtk_ruby_zip.rb index 4ce1cae0..a86f0a9e 100755 --- a/samples/gtk_ruby_zip.rb +++ b/samples/gtk_ruby_zip.rb @@ -18,14 +18,14 @@ def initialize add(box) @zipfile = nil - @buttonPanel = ButtonPanel.new - @buttonPanel.openButton.signal_connect(Gtk::Button::SIGNAL_CLICKED) do + @button_panel = ButtonPanel.new + @button_panel.open_button.signal_connect(Gtk::Button::SIGNAL_CLICKED) do show_file_selector end - @buttonPanel.extractButton.signal_connect(Gtk::Button::SIGNAL_CLICKED) do + @button_panel.extract_button.signal_connect(Gtk::Button::SIGNAL_CLICKED) do puts 'Not implemented!' end - box.pack_start(@buttonPanel, false, false, 0) + box.pack_start(@button_panel, false, false, 0) sw = Gtk::ScrolledWindow.new sw.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC) @@ -42,27 +42,27 @@ def initialize end class ButtonPanel < Gtk::HButtonBox - attr_reader :openButton, :extractButton + attr_reader :open_button, :extract_button def initialize super set_layout(Gtk::BUTTONBOX_START) set_spacing(0) - @openButton = Gtk::Button.new('Open archive') - @extractButton = Gtk::Button.new('Extract entry') - pack_start(@openButton) - pack_start(@extractButton) + @open_button = Gtk::Button.new('Open archive') + @extract_button = Gtk::Button.new('Extract entry') + pack_start(@open_button) + pack_start(@extract_button) end end def show_file_selector - @fileSelector = Gtk::FileSelection.new('Open zip file') - @fileSelector.show - @fileSelector.ok_button.signal_connect(Gtk::Button::SIGNAL_CLICKED) do - open_zip(@fileSelector.filename) - @fileSelector.destroy + @file_selector = Gtk::FileSelection.new('Open zip file') + @file_selector.show + @file_selector.ok_button.signal_connect(Gtk::Button::SIGNAL_CLICKED) do + open_zip(@file_selector.filename) + @file_selector.destroy end - @fileSelector.cancel_button.signal_connect(Gtk::Button::SIGNAL_CLICKED) do - @fileSelector.destroy + @file_selector.cancel_button.signal_connect(Gtk::Button::SIGNAL_CLICKED) do + @file_selector.destroy end end @@ -77,8 +77,8 @@ def open_zip(filename) end end -mainApp = MainApp.new +main_app = MainApp.new -mainApp.show_all +main_app.show_all Gtk.main From e6f414f539d55028ec33aa527967c536894416a4 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 19 Feb 2020 10:52:49 +0000 Subject: [PATCH 149/469] Fix Naming/VariableName cop in the tests. --- .rubocop_todo.yml | 1 - test/basic_zip_file_test.rb | 9 +- test/case_sensitivity_test.rb | 16 +- test/central_directory_test.rb | 30 +- test/deflater_test.rb | 4 +- test/entry_set_test.rb | 90 ++--- test/file_extract_directory_test.rb | 6 +- test/file_extract_test.rb | 30 +- test/file_test.rb | 333 ++++++++++--------- test/filesystem/dir_iterator_test.rb | 42 +-- test/filesystem/directory_test.rb | 10 +- test/filesystem/file_nonmutating_test.rb | 36 +- test/gentestfiles.rb | 16 +- test/ioextras/abstract_input_stream_test.rb | 16 +- test/ioextras/abstract_output_stream_test.rb | 8 +- test/local_entry_test.rb | 78 +++-- test/settings_test.rb | 8 +- test/test_helper.rb | 74 ++--- 18 files changed, 428 insertions(+), 379 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 1d169a87..67bd0811 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -44,7 +44,6 @@ Naming/AccessorMethodName: Naming/VariableName: Exclude: - 'lib/**/*.rb' - - 'test/**/*.rb' # Offense count: 7 # Configuration parameters: EnforcedStyle. diff --git a/test/basic_zip_file_test.rb b/test/basic_zip_file_test.rb index 3d21ae89..994728a3 100644 --- a/test/basic_zip_file_test.rb +++ b/test/basic_zip_file_test.rb @@ -5,7 +5,6 @@ class BasicZipFileTest < MiniTest::Test def setup @zip_file = ::Zip::File.new(TestZipFile::TEST_ZIP2.zip_name) - @testEntryNameIndex = 0 end def test_entries @@ -50,11 +49,9 @@ def test_get_input_stream end def test_get_input_stream_block - fileAndEntryName = @zip_file.entries.first.name - @zip_file.get_input_stream(fileAndEntryName) do |zis| - assert_entry_contents_for_stream(fileAndEntryName, - zis, - fileAndEntryName) + name = @zip_file.entries.first.name + @zip_file.get_input_stream(name) do |zis| + assert_entry_contents_for_stream(name, zis, name) end end end diff --git a/test/case_sensitivity_test.rb b/test/case_sensitivity_test.rb index 5966c4fa..1c89551a 100644 --- a/test/case_sensitivity_test.rb +++ b/test/case_sensitivity_test.rb @@ -20,12 +20,12 @@ def test_add_case_sensitive SRC_FILES.each { |fn, en| zf.add(en, fn) } zf.close - zfRead = ::Zip::File.new(EMPTY_FILENAME) - assert_equal(SRC_FILES.size, zfRead.entries.length) + zf_read = ::Zip::File.new(EMPTY_FILENAME) + assert_equal(SRC_FILES.size, zf_read.entries.length) SRC_FILES.each_with_index do |a, i| - assert_equal(a.last, zfRead.entries[i].name) + assert_equal(a.last, zf_read.entries[i].name) AssertEntry.assert_contents(a.first, - zfRead.get_input_stream(a.last, &:read)) + zf_read.get_input_stream(a.last, &:read)) end end @@ -53,11 +53,11 @@ def test_add_case_sensitive_read_case_insensitive ::Zip.case_insensitive_match = true - zfRead = ::Zip::File.new(EMPTY_FILENAME) - assert_equal(SRC_FILES.collect { |_fn, en| en.downcase }.uniq.size, zfRead.entries.length) - assert_equal(SRC_FILES.last.last.downcase, zfRead.entries.first.name.downcase) + zf_read = ::Zip::File.new(EMPTY_FILENAME) + assert_equal(SRC_FILES.collect { |_fn, en| en.downcase }.uniq.size, zf_read.entries.length) + assert_equal(SRC_FILES.last.last.downcase, zf_read.entries.first.name.downcase) AssertEntry.assert_contents( - SRC_FILES.last.first, zfRead.get_input_stream(SRC_FILES.last.last, &:read) + SRC_FILES.last.first, zf_read.get_input_stream(SRC_FILES.last.last, &:read) ) end diff --git a/test/central_directory_test.rb b/test/central_directory_test.rb index 9f75a299..c4f7afa0 100644 --- a/test/central_directory_test.rb +++ b/test/central_directory_test.rb @@ -41,12 +41,18 @@ def test_write_to_stream entries = [::Zip::Entry.new('file.zip', 'flimse', 'myComment', 'somethingExtra'), ::Zip::Entry.new('file.zip', 'secondEntryName'), ::Zip::Entry.new('file.zip', 'lastEntry.txt', 'Has a comment too')] + cdir = ::Zip::CentralDirectory.new(entries, 'my zip comment') - File.open('test/data/generated/cdirtest.bin', 'wb') { |f| cdir.write_to_stream(f) } - cdirReadback = ::Zip::CentralDirectory.new - File.open('test/data/generated/cdirtest.bin', 'rb') { |f| cdirReadback.read_from_stream(f) } + File.open('test/data/generated/cdirtest.bin', 'wb') do |f| + cdir.write_to_stream(f) + end + + cdir_readback = ::Zip::CentralDirectory.new + File.open('test/data/generated/cdirtest.bin', 'rb') do |f| + cdir_readback.read_from_stream(f) + end - assert_equal(cdir.entries.sort, cdirReadback.entries.sort) + assert_equal(cdir.entries.sort, cdir_readback.entries.sort) end def test_write64_to_stream @@ -58,13 +64,19 @@ def test_write64_to_stream [0, 250, 18_000_000_300, 33_000_000_350].each_with_index do |offset, index| entries[index].local_header_offset = offset end + cdir = ::Zip::CentralDirectory.new(entries, 'zip comment') - File.open('test/data/generated/cdir64test.bin', 'wb') { |f| cdir.write_to_stream(f) } - cdirReadback = ::Zip::CentralDirectory.new - File.open('test/data/generated/cdir64test.bin', 'rb') { |f| cdirReadback.read_from_stream(f) } + File.open('test/data/generated/cdir64test.bin', 'wb') do |f| + cdir.write_to_stream(f) + end + + cdir_readback = ::Zip::CentralDirectory.new + File.open('test/data/generated/cdir64test.bin', 'rb') do |f| + cdir_readback.read_from_stream(f) + end - assert_equal(cdir.entries.sort, cdirReadback.entries.sort) - assert_equal(::Zip::VERSION_NEEDED_TO_EXTRACT_ZIP64, cdirReadback.instance_variable_get(:@version_needed_for_extract)) + assert_equal(cdir.entries.sort, cdir_readback.entries.sort) + assert_equal(::Zip::VERSION_NEEDED_TO_EXTRACT_ZIP64, cdir_readback.instance_variable_get(:@version_needed_for_extract)) end def test_equality diff --git a/test/deflater_test.rb b/test/deflater_test.rb index b76dcc36..2506f920 100644 --- a/test/deflater_test.rb +++ b/test/deflater_test.rb @@ -11,8 +11,8 @@ class DeflaterTest < MiniTest::Test def test_output_operator txt = load_file('test/data/file2.txt') deflate(txt, DEFLATER_TEST_FILE) - inflatedTxt = inflate(DEFLATER_TEST_FILE) - assert_equal(txt, inflatedTxt) + inflated_txt = inflate(DEFLATER_TEST_FILE) + assert_equal(txt, inflated_txt) end def test_default_compression diff --git a/test/entry_set_test.rb b/test/entry_set_test.rb index 6501ab86..4f137902 100644 --- a/test/entry_set_test.rb +++ b/test/entry_set_test.rb @@ -11,7 +11,7 @@ class ZipEntrySetTest < MiniTest::Test ] def setup - @zipEntrySet = ::Zip::EntrySet.new(ZIP_ENTRIES) + @zip_entry_set = ::Zip::EntrySet.new(ZIP_ENTRIES) end def teardown @@ -19,15 +19,15 @@ def teardown end def test_include - assert(@zipEntrySet.include?(ZIP_ENTRIES.first)) - assert(!@zipEntrySet.include?(::Zip::Entry.new('different.zip', 'different', 'aComment'))) + assert(@zip_entry_set.include?(ZIP_ENTRIES.first)) + assert(!@zip_entry_set.include?(::Zip::Entry.new('different.zip', 'different', 'aComment'))) end def test_size - assert_equal(ZIP_ENTRIES.size, @zipEntrySet.size) - assert_equal(ZIP_ENTRIES.size, @zipEntrySet.length) - @zipEntrySet << ::Zip::Entry.new('a', 'b', 'c') - assert_equal(ZIP_ENTRIES.size + 1, @zipEntrySet.length) + assert_equal(ZIP_ENTRIES.size, @zip_entry_set.size) + assert_equal(ZIP_ENTRIES.size, @zip_entry_set.length) + @zip_entry_set << ::Zip::Entry.new('a', 'b', 'c') + assert_equal(ZIP_ENTRIES.size + 1, @zip_entry_set.length) end def test_add @@ -41,20 +41,20 @@ def test_add end def test_delete - assert_equal(ZIP_ENTRIES.size, @zipEntrySet.size) - entry = @zipEntrySet.delete(ZIP_ENTRIES.first) - assert_equal(ZIP_ENTRIES.size - 1, @zipEntrySet.size) + assert_equal(ZIP_ENTRIES.size, @zip_entry_set.size) + entry = @zip_entry_set.delete(ZIP_ENTRIES.first) + assert_equal(ZIP_ENTRIES.size - 1, @zip_entry_set.size) assert_equal(ZIP_ENTRIES.first, entry) - entry = @zipEntrySet.delete(ZIP_ENTRIES.first) - assert_equal(ZIP_ENTRIES.size - 1, @zipEntrySet.size) + entry = @zip_entry_set.delete(ZIP_ENTRIES.first) + assert_equal(ZIP_ENTRIES.size - 1, @zip_entry_set.size) assert_nil(entry) end def test_each # Used each instead each_with_index due the bug in jRuby count = 0 - @zipEntrySet.each do |entry| + @zip_entry_set.each do |entry| assert(ZIP_ENTRIES.include?(entry)) count += 1 end @@ -62,57 +62,57 @@ def test_each end def test_entries - assert_equal(ZIP_ENTRIES, @zipEntrySet.entries) + assert_equal(ZIP_ENTRIES, @zip_entry_set.entries) end def test_find_entry entries = [::Zip::Entry.new('zipfile.zip', 'MiXeDcAsEnAmE', 'comment1')] ::Zip.case_insensitive_match = true - zipEntrySet = ::Zip::EntrySet.new(entries) - assert_equal(entries[0], zipEntrySet.find_entry('MiXeDcAsEnAmE')) - assert_equal(entries[0], zipEntrySet.find_entry('mixedcasename')) + zip_entry_set = ::Zip::EntrySet.new(entries) + assert_equal(entries[0], zip_entry_set.find_entry('MiXeDcAsEnAmE')) + assert_equal(entries[0], zip_entry_set.find_entry('mixedcasename')) ::Zip.case_insensitive_match = false - zipEntrySet = ::Zip::EntrySet.new(entries) - assert_equal(entries[0], zipEntrySet.find_entry('MiXeDcAsEnAmE')) - assert_nil(zipEntrySet.find_entry('mixedcasename')) + zip_entry_set = ::Zip::EntrySet.new(entries) + assert_equal(entries[0], zip_entry_set.find_entry('MiXeDcAsEnAmE')) + assert_nil(zip_entry_set.find_entry('mixedcasename')) end def test_entries_with_sort ::Zip.sort_entries = true - assert_equal(ZIP_ENTRIES.sort, @zipEntrySet.entries) + assert_equal(ZIP_ENTRIES.sort, @zip_entry_set.entries) ::Zip.sort_entries = false - assert_equal(ZIP_ENTRIES, @zipEntrySet.entries) + assert_equal(ZIP_ENTRIES, @zip_entry_set.entries) end def test_entries_sorted_in_each ::Zip.sort_entries = true arr = [] - @zipEntrySet.each do |entry| + @zip_entry_set.each do |entry| arr << entry end assert_equal(ZIP_ENTRIES.sort, arr) end def test_compound - newEntry = ::Zip::Entry.new('zf.zip', 'new entry', "new entry's comment") - assert_equal(ZIP_ENTRIES.size, @zipEntrySet.size) - @zipEntrySet << newEntry - assert_equal(ZIP_ENTRIES.size + 1, @zipEntrySet.size) - assert(@zipEntrySet.include?(newEntry)) + new_entry = ::Zip::Entry.new('zf.zip', 'new entry', "new entry's comment") + assert_equal(ZIP_ENTRIES.size, @zip_entry_set.size) + @zip_entry_set << new_entry + assert_equal(ZIP_ENTRIES.size + 1, @zip_entry_set.size) + assert(@zip_entry_set.include?(new_entry)) - @zipEntrySet.delete(newEntry) - assert_equal(ZIP_ENTRIES.size, @zipEntrySet.size) + @zip_entry_set.delete(new_entry) + assert_equal(ZIP_ENTRIES.size, @zip_entry_set.size) end def test_dup - copy = @zipEntrySet.dup - assert_equal(@zipEntrySet, copy) + copy = @zip_entry_set.dup + assert_equal(@zip_entry_set, copy) # demonstrate that this is a deep copy copy.entries[0].name = 'a totally different name' - assert(@zipEntrySet != copy) + assert(@zip_entry_set != copy) end def test_parent @@ -121,15 +121,15 @@ def test_parent ::Zip::Entry.new('zf.zip', 'a/b/'), ::Zip::Entry.new('zf.zip', 'a/b/c/') ] - entrySet = ::Zip::EntrySet.new(entries) + entry_set = ::Zip::EntrySet.new(entries) - assert_nil(entrySet.parent(entries[0])) - assert_equal(entries[0], entrySet.parent(entries[1])) - assert_equal(entries[1], entrySet.parent(entries[2])) + assert_nil(entry_set.parent(entries[0])) + assert_equal(entries[0], entry_set.parent(entries[1])) + assert_equal(entries[1], entry_set.parent(entries[2])) end def test_glob - res = @zipEntrySet.glob('name[2-4]') + res = @zip_entry_set.glob('name[2-4]') assert_equal(3, res.size) assert_equal(ZIP_ENTRIES[1, 3].sort, res.sort) end @@ -141,13 +141,13 @@ def test_glob2 ::Zip::Entry.new('zf.zip', 'a/b/c/'), ::Zip::Entry.new('zf.zip', 'a/b/c/c1') ] - entrySet = ::Zip::EntrySet.new(entries) + entry_set = ::Zip::EntrySet.new(entries) - assert_equal(entries[0, 1], entrySet.glob('*')) - # assert_equal(entries[FIXME], entrySet.glob("**")) - # res = entrySet.glob('a*') + assert_equal(entries[0, 1], entry_set.glob('*')) + # assert_equal(entries[FIXME], entry_set.glob("**")) + # res = entry_set.glob('a*') # assert_equal(entries.size, res.size) - # assert_equal(entrySet.map { |e| e.name }, res.map { |e| e.name }) + # assert_equal(entry_set.map { |e| e.name }, res.map { |e| e.name }) end def test_glob3 @@ -156,8 +156,8 @@ def test_glob3 ::Zip::Entry.new('zf.zip', 'a/b'), ::Zip::Entry.new('zf.zip', 'a/c') ] - entrySet = ::Zip::EntrySet.new(entries) + entry_set = ::Zip::EntrySet.new(entries) - assert_equal(entries[0, 2].sort, entrySet.glob('a/{a,b}').sort) + assert_equal(entries[0, 2].sort, entry_set.glob('a/{a,b}').sort) end end diff --git a/test/file_extract_directory_test.rb b/test/file_extract_directory_test.rb index 8e5b7775..02a3fd0d 100644 --- a/test/file_extract_directory_test.rb +++ b/test/file_extract_directory_test.rb @@ -41,14 +41,14 @@ def test_extract_directory_exists_as_file def test_extract_directory_exists_as_file_overwrite File.open(TEST_OUT_NAME, 'w') { |f| f.puts 'something' } - gotCalled = false + called = false extract_test_dir do |entry, dest_path| - gotCalled = true + called = true assert_equal(TEST_OUT_NAME, dest_path) assert(entry.directory?) true end - assert(gotCalled) + assert(called) assert(File.directory?(TEST_OUT_NAME)) end end diff --git a/test/file_extract_test.rb b/test/file_extract_test.rb index e166debe..0e697187 100644 --- a/test/file_extract_test.rb +++ b/test/file_extract_test.rb @@ -34,8 +34,8 @@ def test_extract end def test_extract_exists - writtenText = 'written text' - ::File.open(EXTRACTED_FILENAME, 'w') { |f| f.write(writtenText) } + text = 'written text' + ::File.open(EXTRACTED_FILENAME, 'w') { |f| f.write(text) } assert_raises(::Zip::DestinationFileExistsError) do ::Zip::File.open(TEST_ZIP.zip_name) do |zf| @@ -43,26 +43,26 @@ def test_extract_exists end end File.open(EXTRACTED_FILENAME, 'r') do |f| - assert_equal(writtenText, f.read) + assert_equal(text, f.read) end end def test_extract_exists_overwrite - writtenText = 'written text' - ::File.open(EXTRACTED_FILENAME, 'w') { |f| f.write(writtenText) } + text = 'written text' + ::File.open(EXTRACTED_FILENAME, 'w') { |f| f.write(text) } - gotCalledCorrectly = false + called_correctly = false ::Zip::File.open(TEST_ZIP.zip_name) do |zf| zf.extract(zf.entries.first, EXTRACTED_FILENAME) do |entry, extract_loc| - gotCalledCorrectly = zf.entries.first == entry && - extract_loc == EXTRACTED_FILENAME + called_correctly = zf.entries.first == entry && + extract_loc == EXTRACTED_FILENAME true end end - assert(gotCalledCorrectly) + assert(called_correctly) ::File.open(EXTRACTED_FILENAME, 'r') do |f| - assert(writtenText != f.read) + assert(text != f.read) end end @@ -74,15 +74,15 @@ def test_extract_non_entry end def test_extract_non_entry_2 - outFile = 'outfile' + out_file = 'outfile' assert_raises(Errno::ENOENT) do zf = ::Zip::File.new(TEST_ZIP.zip_name) - nonEntry = 'hotdog-diddelidoo' - assert(!zf.entries.include?(nonEntry)) - zf.extract(nonEntry, outFile) + non_entry = 'hotdog-diddelidoo' + assert(!zf.entries.include?(non_entry)) + zf.extract(non_entry, out_file) zf.close end - assert(!File.exist?(outFile)) + assert(!File.exist?(out_file)) end def test_extract_incorrect_size diff --git a/test/file_test.rb b/test/file_test.rb index 20fbf48f..c11af675 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -22,9 +22,9 @@ def test_create_from_scratch_to_buffer ::File.open(EMPTY_FILENAME, 'wb') { |file| file.write buffer.string } - zfRead = ::Zip::File.new(EMPTY_FILENAME) - assert_equal(comment, zfRead.comment) - assert_equal(2, zfRead.entries.length) + zf_read = ::Zip::File.new(EMPTY_FILENAME) + assert_equal(comment, zf_read.comment) + assert_equal(2, zf_read.entries.length) end def test_create_from_scratch @@ -36,9 +36,9 @@ def test_create_from_scratch zf.comment = comment zf.close - zfRead = ::Zip::File.new(EMPTY_FILENAME) - assert_equal(comment, zfRead.comment) - assert_equal(2, zfRead.entries.length) + zf_read = ::Zip::File.new(EMPTY_FILENAME) + assert_equal(comment, zf_read.comment) + assert_equal(2, zf_read.entries.length) end def test_create_from_scratch_with_old_create_parameter @@ -50,9 +50,9 @@ def test_create_from_scratch_with_old_create_parameter zf.comment = comment zf.close - zfRead = ::Zip::File.new(EMPTY_FILENAME) - assert_equal(comment, zfRead.comment) - assert_equal(2, zfRead.entries.length) + zf_read = ::Zip::File.new(EMPTY_FILENAME) + assert_equal(comment, zf_read.comment) + assert_equal(2, zf_read.entries.length) end def test_get_input_stream_stored_with_gpflag_bit3 @@ -62,26 +62,26 @@ def test_get_input_stream_stored_with_gpflag_bit3 end def test_get_output_stream - entryCount = nil + count = nil ::Zip::File.open(TEST_ZIP.zip_name) do |zf| - entryCount = zf.size - zf.get_output_stream('newEntry.txt') do |os| - os.write 'Putting stuff in newEntry.txt' + count = zf.size + zf.get_output_stream('new_entry.txt') do |os| + os.write 'Putting stuff in new_entry.txt' end - assert_equal(entryCount + 1, zf.size) - assert_equal('Putting stuff in newEntry.txt', zf.read('newEntry.txt')) + assert_equal(count + 1, zf.size) + assert_equal('Putting stuff in new_entry.txt', zf.read('new_entry.txt')) zf.get_output_stream(zf.get_entry('test/data/generated/empty.txt')) do |os| os.write 'Putting stuff in data/generated/empty.txt' end - assert_equal(entryCount + 1, zf.size) + assert_equal(count + 1, zf.size) assert_equal('Putting stuff in data/generated/empty.txt', zf.read('test/data/generated/empty.txt')) custom_entry_args = [TEST_COMMENT, TEST_EXTRA, TEST_COMPRESSED_SIZE, TEST_CRC, ::Zip::Entry::STORED, TEST_SIZE, TEST_TIME] zf.get_output_stream('entry_with_custom_args.txt', nil, *custom_entry_args) do |os| os.write 'Some data' end - assert_equal(entryCount + 2, zf.size) + assert_equal(count + 2, zf.size) entry = zf.get_entry('entry_with_custom_args.txt') assert_equal(custom_entry_args[0], entry.comment) assert_equal(custom_entry_args[2], entry.compressed_size) @@ -96,8 +96,8 @@ def test_get_output_stream end ::Zip::File.open(TEST_ZIP.zip_name) do |zf| - assert_equal(entryCount + 3, zf.size) - assert_equal('Putting stuff in newEntry.txt', zf.read('newEntry.txt')) + assert_equal(count + 3, zf.size) + assert_equal('Putting stuff in new_entry.txt', zf.read('new_entry.txt')) assert_equal('Putting stuff in data/generated/empty.txt', zf.read('test/data/generated/empty.txt')) assert_equal(File.open('test/data/generated/5entry.zip', 'rb').read, zf.read('entry.bin')) end @@ -189,52 +189,52 @@ def test_cleans_up_tempfiles_after_close end def test_add - srcFile = 'test/data/file2.txt' - entryName = 'newEntryName.rb' - assert(::File.exist?(srcFile)) + src_file = 'test/data/file2.txt' + entry_name = 'newEntryName.rb' + assert(::File.exist?(src_file)) zf = ::Zip::File.new(EMPTY_FILENAME, ::Zip::File::CREATE) - zf.add(entryName, srcFile) + zf.add(entry_name, src_file) zf.close - zfRead = ::Zip::File.new(EMPTY_FILENAME) - assert_equal('', zfRead.comment) - assert_equal(1, zfRead.entries.length) - assert_equal(entryName, zfRead.entries.first.name) - AssertEntry.assert_contents(srcFile, - zfRead.get_input_stream(entryName, &:read)) + zf_read = ::Zip::File.new(EMPTY_FILENAME) + assert_equal('', zf_read.comment) + assert_equal(1, zf_read.entries.length) + assert_equal(entry_name, zf_read.entries.first.name) + AssertEntry.assert_contents(src_file, + zf_read.get_input_stream(entry_name, &:read)) end def test_add_stored - srcFile = 'test/data/file2.txt' - entryName = 'newEntryName.rb' - assert(::File.exist?(srcFile)) + src_file = 'test/data/file2.txt' + entry_name = 'newEntryName.rb' + assert(::File.exist?(src_file)) zf = ::Zip::File.new(EMPTY_FILENAME, ::Zip::File::CREATE) - zf.add_stored(entryName, srcFile) + zf.add_stored(entry_name, src_file) zf.close - zfRead = ::Zip::File.new(EMPTY_FILENAME) - entry = zfRead.entries.first - assert_equal('', zfRead.comment) - assert_equal(1, zfRead.entries.length) - assert_equal(entryName, entry.name) - assert_equal(File.size(srcFile), entry.size) + zf_read = ::Zip::File.new(EMPTY_FILENAME) + entry = zf_read.entries.first + assert_equal('', zf_read.comment) + assert_equal(1, zf_read.entries.length) + assert_equal(entry_name, entry.name) + assert_equal(File.size(src_file), entry.size) assert_equal(entry.size, entry.compressed_size) assert_equal(::Zip::Entry::STORED, entry.compression_method) - AssertEntry.assert_contents(srcFile, - zfRead.get_input_stream(entryName, &:read)) + AssertEntry.assert_contents(src_file, + zf_read.get_input_stream(entry_name, &:read)) end def test_recover_permissions_after_add_files_to_archive - srcZip = TEST_ZIP.zip_name - ::File.chmod(0o664, srcZip) - srcFile = 'test/data/file2.txt' - entryName = 'newEntryName.rb' - assert_equal(::File.stat(srcZip).mode, 0o100664) - assert(::File.exist?(srcZip)) - zf = ::Zip::File.new(srcZip, ::Zip::File::CREATE) - zf.add(entryName, srcFile) + src_zip = TEST_ZIP.zip_name + ::File.chmod(0o664, src_zip) + src_file = 'test/data/file2.txt' + entry_name = 'newEntryName.rb' + assert_equal(::File.stat(src_zip).mode, 0o100664) + assert(::File.exist?(src_zip)) + zf = ::Zip::File.new(src_zip, ::Zip::File::CREATE) + zf.add(entry_name, src_file) zf.close - assert_equal(::File.stat(srcZip).mode, 0o100664) + assert_equal(::File.stat(src_zip).mode, 0o100664) end def test_add_existing_entry_name @@ -246,18 +246,18 @@ def test_add_existing_entry_name end def test_add_existing_entry_name_replace - gotCalled = false - replacedEntry = nil + called = false + replaced_entry = nil ::Zip::File.open(TEST_ZIP.zip_name) do |zf| - replacedEntry = zf.entries.first.name - zf.add(replacedEntry, 'test/data/file2.txt') do - gotCalled = true + replaced_entry = zf.entries.first.name + zf.add(replaced_entry, 'test/data/file2.txt') do + called = true true end end - assert(gotCalled) + assert(called) ::Zip::File.open(TEST_ZIP.zip_name) do |zf| - assert_contains(zf, replacedEntry, 'test/data/file2.txt') + assert_contains(zf, replaced_entry, 'test/data/file2.txt') end end @@ -265,51 +265,55 @@ def test_add_directory ::Zip::File.open(TEST_ZIP.zip_name) do |zf| zf.add(TestFiles::EMPTY_TEST_DIR, TestFiles::EMPTY_TEST_DIR) end + ::Zip::File.open(TEST_ZIP.zip_name) do |zf| - dirEntry = zf.entries.detect { |e| e.name == TestFiles::EMPTY_TEST_DIR + '/' } - assert(dirEntry.directory?) + dir_entry = zf.entries.detect do |e| + e.name == TestFiles::EMPTY_TEST_DIR + '/' + end + + assert(dir_entry.directory?) end end def test_remove - entryToRemove, *remainingEntries = TEST_ZIP.entry_names + entry, *remaining = TEST_ZIP.entry_names FileUtils.cp(TestZipFile::TEST_ZIP2.zip_name, TEST_ZIP.zip_name) zf = ::Zip::File.new(TEST_ZIP.zip_name) - assert(zf.entries.map(&:name).include?(entryToRemove)) - zf.remove(entryToRemove) - assert(!zf.entries.map(&:name).include?(entryToRemove)) - assert_equal(zf.entries.map(&:name).sort, remainingEntries.sort) + assert(zf.entries.map(&:name).include?(entry)) + zf.remove(entry) + assert(!zf.entries.map(&:name).include?(entry)) + assert_equal(zf.entries.map(&:name).sort, remaining.sort) zf.close - zfRead = ::Zip::File.new(TEST_ZIP.zip_name) - assert(!zfRead.entries.map(&:name).include?(entryToRemove)) - assert_equal(zfRead.entries.map(&:name).sort, remainingEntries.sort) - zfRead.close + zf_read = ::Zip::File.new(TEST_ZIP.zip_name) + assert(!zf_read.entries.map(&:name).include?(entry)) + assert_equal(zf_read.entries.map(&:name).sort, remaining.sort) + zf_read.close end def test_rename - entryToRename, * = TEST_ZIP.entry_names + entry, * = TEST_ZIP.entry_names zf = ::Zip::File.new(TEST_ZIP.zip_name) - assert(zf.entries.map(&:name).include?(entryToRename)) + assert(zf.entries.map(&:name).include?(entry)) - contents = zf.read(entryToRename) - newName = 'changed entry name' - assert(!zf.entries.map(&:name).include?(newName)) + contents = zf.read(entry) + new_name = 'changed entry name' + assert(!zf.entries.map(&:name).include?(new_name)) - zf.rename(entryToRename, newName) - assert(zf.entries.map(&:name).include?(newName)) + zf.rename(entry, new_name) + assert(zf.entries.map(&:name).include?(new_name)) - assert_equal(contents, zf.read(newName)) + assert_equal(contents, zf.read(new_name)) zf.close - zfRead = ::Zip::File.new(TEST_ZIP.zip_name) - assert(zfRead.entries.map(&:name).include?(newName)) - assert_equal(contents, zfRead.read(newName)) - zfRead.close + zf_read = ::Zip::File.new(TEST_ZIP.zip_name) + assert(zf_read.entries.map(&:name).include?(new_name)) + assert_equal(contents, zf_read.read(new_name)) + zf_read.close end def test_rename_with_each @@ -342,8 +346,8 @@ def test_rename_with_each end def test_rename_to_existing_entry - oldEntries = nil - ::Zip::File.open(TEST_ZIP.zip_name) { |zf| oldEntries = zf.entries } + old_entries = nil + ::Zip::File.open(TEST_ZIP.zip_name) { |zf| old_entries = zf.entries } assert_raises(::Zip::EntryExistsError) do ::Zip::File.open(TEST_ZIP.zip_name) do |zf| @@ -352,38 +356,38 @@ def test_rename_to_existing_entry end ::Zip::File.open(TEST_ZIP.zip_name) do |zf| - assert_equal(oldEntries.sort.map(&:name), zf.entries.sort.map(&:name)) + assert_equal(old_entries.sort.map(&:name), zf.entries.sort.map(&:name)) end end def test_rename_to_existing_entry_overwrite - oldEntries = nil - ::Zip::File.open(TEST_ZIP.zip_name) { |zf| oldEntries = zf.entries } + old_entries = nil + ::Zip::File.open(TEST_ZIP.zip_name) { |zf| old_entries = zf.entries } - gotCalled = false - renamedEntryName = nil + called = false + new_entry_name = nil ::Zip::File.open(TEST_ZIP.zip_name) do |zf| - renamedEntryName = zf.entries[0].name + new_entry_name = zf.entries[0].name zf.rename(zf.entries[0], zf.entries[1].name) do - gotCalled = true + called = true true end end - assert(gotCalled) - oldEntries.delete_if { |e| e.name == renamedEntryName } + assert(called) + old_entries.delete_if { |e| e.name == new_entry_name } ::Zip::File.open(TEST_ZIP.zip_name) do |zf| - assert_equal(oldEntries.sort.map(&:name), + assert_equal(old_entries.sort.map(&:name), zf.entries.sort.map(&:name)) end end def test_rename_non_entry - nonEntry = 'bogusEntry' + non_entry = 'bogusEntry' target_entry = 'target_entryName' zf = ::Zip::File.new(TEST_ZIP.zip_name) - assert(!zf.entries.include?(nonEntry)) - assert_raises(Errno::ENOENT) { zf.rename(nonEntry, target_entry) } + assert(!zf.entries.include?(non_entry)) + assert_raises(Errno::ENOENT) { zf.rename(non_entry, target_entry) } zf.commit assert(!zf.entries.include?(target_entry)) ensure @@ -399,45 +403,52 @@ def test_rename_entry_to_existing_entry end def test_replace - entryToReplace = TEST_ZIP.entry_names[2] - newEntrySrcFilename = 'test/data/file2.txt' + replace_entry = TEST_ZIP.entry_names[2] + replace_src = 'test/data/file2.txt' zf = ::Zip::File.new(TEST_ZIP.zip_name) - zf.replace(entryToReplace, newEntrySrcFilename) + zf.replace(replace_entry, replace_src) zf.close - zfRead = ::Zip::File.new(TEST_ZIP.zip_name) - AssertEntry.assert_contents(newEntrySrcFilename, - zfRead.get_input_stream(entryToReplace, &:read)) - AssertEntry.assert_contents(TEST_ZIP.entry_names[0], - zfRead.get_input_stream(TEST_ZIP.entry_names[0], - &:read)) - AssertEntry.assert_contents(TEST_ZIP.entry_names[1], - zfRead.get_input_stream(TEST_ZIP.entry_names[1], - &:read)) - AssertEntry.assert_contents(TEST_ZIP.entry_names[3], - zfRead.get_input_stream(TEST_ZIP.entry_names[3], - &:read)) - zfRead.close + zf_read = ::Zip::File.new(TEST_ZIP.zip_name) + AssertEntry.assert_contents( + replace_src, + zf_read.get_input_stream(replace_entry, &:read) + ) + AssertEntry.assert_contents( + TEST_ZIP.entry_names[0], + zf_read.get_input_stream(TEST_ZIP.entry_names[0], &:read) + ) + AssertEntry.assert_contents( + TEST_ZIP.entry_names[1], + zf_read.get_input_stream(TEST_ZIP.entry_names[1], &:read) + ) + AssertEntry.assert_contents( + TEST_ZIP.entry_names[3], + zf_read.get_input_stream(TEST_ZIP.entry_names[3], &:read) + ) + zf_read.close end def test_replace_non_entry - entryToReplace = 'nonExistingEntryname' + replace_entry = 'nonExistingEntryname' ::Zip::File.open(TEST_ZIP.zip_name) do |zf| - assert_raises(Errno::ENOENT) { zf.replace(entryToReplace, 'test/data/file2.txt') } + assert_raises(Errno::ENOENT) do + zf.replace(replace_entry, 'test/data/file2.txt') + end end end def test_commit - newName = 'renamedFirst' + new_name = 'renamedFirst' zf = ::Zip::File.new(TEST_ZIP.zip_name) - oldName = zf.entries.first - zf.rename(oldName, newName) + old_name = zf.entries.first + zf.rename(old_name, new_name) zf.commit - zfRead = ::Zip::File.new(TEST_ZIP.zip_name) - refute_nil(zfRead.entries.detect { |e| e.name == newName }) - assert_nil(zfRead.entries.detect { |e| e.name == oldName }) - zfRead.close + zf_read = ::Zip::File.new(TEST_ZIP.zip_name) + refute_nil(zf_read.entries.detect { |e| e.name == new_name }) + assert_nil(zf_read.entries.detect { |e| e.name == old_name }) + zf_read.close zf.close res = system("unzip -tqq #{TEST_ZIP.zip_name}") @@ -466,17 +477,17 @@ def test_double_commit_zip64 end def test_write_buffer - newName = 'renamedFirst' + new_name = 'renamedFirst' zf = ::Zip::File.new(TEST_ZIP.zip_name) - oldName = zf.entries.first - zf.rename(oldName, newName) + old_name = zf.entries.first + zf.rename(old_name, new_name) io = ::StringIO.new('') buffer = zf.write_buffer(io) File.open(TEST_ZIP.zip_name, 'wb') { |f| f.write buffer.string } - zfRead = ::Zip::File.new(TEST_ZIP.zip_name) - refute_nil(zfRead.entries.detect { |e| e.name == newName }) - assert_nil(zfRead.entries.detect { |e| e.name == oldName }) - zfRead.close + zf_read = ::Zip::File.new(TEST_ZIP.zip_name) + refute_nil(zf_read.entries.detect { |e| e.name == new_name }) + assert_nil(zf_read.entries.detect { |e| e.name == old_name }) + zf_read.close zf.close end @@ -503,52 +514,58 @@ def test_commit_use_zip_entry # end def test_compound1 - renamedName = 'renamedName' + renamed_name = 'renamed_name' filename_to_remove = '' + begin zf = ::Zip::File.new(TEST_ZIP.zip_name) - originalEntries = zf.entries.dup + orig_entries = zf.entries.dup assert_not_contains(zf, TestFiles::RANDOM_ASCII_FILE1) zf.add(TestFiles::RANDOM_ASCII_FILE1, TestFiles::RANDOM_ASCII_FILE1) assert_contains(zf, TestFiles::RANDOM_ASCII_FILE1) - entry_to_rename = zf.entries.find { |entry| entry.name.match('longAscii') } - zf.rename(entry_to_rename, renamedName) - assert_contains(zf, renamedName) + entry_to_rename = zf.entries.find do |entry| + entry.name.match('longAscii') + end + zf.rename(entry_to_rename, renamed_name) + assert_contains(zf, renamed_name) TestFiles::BINARY_TEST_FILES.each do |filename| zf.add(filename, filename) assert_contains(zf, filename) end - assert_contains(zf, originalEntries.last.to_s) - filename_to_remove = originalEntries.map(&:to_s).find { |name| name.match('longBinary') } + assert_contains(zf, orig_entries.last.to_s) + filename_to_remove = orig_entries.map(&:to_s).find do |name| + name.match('longBinary') + end zf.remove(filename_to_remove) assert_not_contains(zf, filename_to_remove) ensure zf.close end + begin - zfRead = ::Zip::File.new(TEST_ZIP.zip_name) - assert_contains(zfRead, TestFiles::RANDOM_ASCII_FILE1) - assert_contains(zfRead, renamedName) + zf_read = ::Zip::File.new(TEST_ZIP.zip_name) + assert_contains(zf_read, TestFiles::RANDOM_ASCII_FILE1) + assert_contains(zf_read, renamed_name) TestFiles::BINARY_TEST_FILES.each do |filename| - assert_contains(zfRead, filename) + assert_contains(zf_read, filename) end - assert_not_contains(zfRead, filename_to_remove) + assert_not_contains(zf_read, filename_to_remove) ensure - zfRead.close + zf_read.close end end def test_compound2 begin zf = ::Zip::File.new(TEST_ZIP.zip_name) - originalEntries = zf.entries.dup + orig_entries = zf.entries.dup - originalEntries.each do |entry| + orig_entries.each do |entry| zf.remove(entry) assert_not_contains(zf, entry) end @@ -560,23 +577,23 @@ def test_compound2 end assert_equal(zf.entries.sort.map(&:name), TestFiles::ASCII_TEST_FILES) - zf.rename(TestFiles::ASCII_TEST_FILES[0], 'newName') + zf.rename(TestFiles::ASCII_TEST_FILES[0], 'new_name') assert_not_contains(zf, TestFiles::ASCII_TEST_FILES[0]) - assert_contains(zf, 'newName') + assert_contains(zf, 'new_name') ensure zf.close end begin - zfRead = ::Zip::File.new(TEST_ZIP.zip_name) - asciiTestFiles = TestFiles::ASCII_TEST_FILES.dup - asciiTestFiles.shift - asciiTestFiles.each do |filename| + zf_read = ::Zip::File.new(TEST_ZIP.zip_name) + ascii_files = TestFiles::ASCII_TEST_FILES.dup + ascii_files.shift + ascii_files.each do |filename| assert_contains(zf, filename) end - assert_contains(zf, 'newName') + assert_contains(zf, 'new_name') ensure - zfRead.close + zf_read.close end end @@ -584,31 +601,31 @@ def test_change_comment ::Zip::File.open(TEST_ZIP.zip_name) do |zf| zf.comment = 'my changed comment' end - zfRead = ::Zip::File.open(TEST_ZIP.zip_name) - assert_equal('my changed comment', zfRead.comment) + zf_read = ::Zip::File.open(TEST_ZIP.zip_name) + assert_equal('my changed comment', zf_read.comment) end def test_preserve_file_order - entryNames = nil + entry_names = nil ::Zip::File.open(TEST_ZIP.zip_name) do |zf| - entryNames = zf.entries.map(&:to_s) + entry_names = zf.entries.map(&:to_s) zf.get_output_stream('a.txt') { |os| os.write 'this is a.txt' } zf.get_output_stream('z.txt') { |os| os.write 'this is z.txt' } zf.get_output_stream('k.txt') { |os| os.write 'this is k.txt' } - entryNames << 'a.txt' << 'z.txt' << 'k.txt' + entry_names << 'a.txt' << 'z.txt' << 'k.txt' end ::Zip::File.open(TEST_ZIP.zip_name) do |zf| - assert_equal(entryNames, zf.entries.map(&:to_s)) + assert_equal(entry_names, zf.entries.map(&:to_s)) entries = zf.entries.sort_by(&:name).reverse entries.each do |e| zf.remove e zf.get_output_stream(e) { |os| os.write 'foo' } end - entryNames = entries.map(&:to_s) + entry_names = entries.map(&:to_s) end ::Zip::File.open(TEST_ZIP.zip_name) do |zf| - assert_equal(entryNames, zf.entries.map(&:to_s)) + assert_equal(entry_names, zf.entries.map(&:to_s)) end end diff --git a/test/filesystem/dir_iterator_test.rb b/test/filesystem/dir_iterator_test.rb index 8d12ce27..e46da426 100644 --- a/test/filesystem/dir_iterator_test.rb +++ b/test/filesystem/dir_iterator_test.rb @@ -5,54 +5,54 @@ class ZipFsDirIteratorTest < MiniTest::Test FILENAME_ARRAY = %w[f1 f2 f3 f4 f5 f6] def setup - @dirIt = ::Zip::FileSystem::ZipFsDirIterator.new(FILENAME_ARRAY) + @dir_iter = ::Zip::FileSystem::ZipFsDirIterator.new(FILENAME_ARRAY) end def test_close - @dirIt.close + @dir_iter.close assert_raises(IOError, 'closed directory') do - @dirIt.each { |e| p e } + @dir_iter.each { |e| p e } end assert_raises(IOError, 'closed directory') do - @dirIt.read + @dir_iter.read end assert_raises(IOError, 'closed directory') do - @dirIt.rewind + @dir_iter.rewind end assert_raises(IOError, 'closed directory') do - @dirIt.seek(0) + @dir_iter.seek(0) end assert_raises(IOError, 'closed directory') do - @dirIt.tell + @dir_iter.tell end end def test_each # Tested through Enumerable.entries - assert_equal(FILENAME_ARRAY, @dirIt.entries) + assert_equal(FILENAME_ARRAY, @dir_iter.entries) end def test_read FILENAME_ARRAY.size.times do |i| - assert_equal(FILENAME_ARRAY[i], @dirIt.read) + assert_equal(FILENAME_ARRAY[i], @dir_iter.read) end end def test_rewind - @dirIt.read - @dirIt.read - assert_equal(FILENAME_ARRAY[2], @dirIt.read) - @dirIt.rewind - assert_equal(FILENAME_ARRAY[0], @dirIt.read) + @dir_iter.read + @dir_iter.read + assert_equal(FILENAME_ARRAY[2], @dir_iter.read) + @dir_iter.rewind + assert_equal(FILENAME_ARRAY[0], @dir_iter.read) end def test_tell_seek - @dirIt.read - @dirIt.read - pos = @dirIt.tell - valAtPos = @dirIt.read - @dirIt.read - @dirIt.seek(pos) - assert_equal(valAtPos, @dirIt.read) + @dir_iter.read + @dir_iter.read + pos = @dir_iter.tell + value = @dir_iter.read + @dir_iter.read + @dir_iter.seek(pos) + assert_equal(value, @dir_iter.read) end end diff --git a/test/filesystem/directory_test.rb b/test/filesystem/directory_test.rb index f36ede53..8ad04d9e 100644 --- a/test/filesystem/directory_test.rb +++ b/test/filesystem/directory_test.rb @@ -65,16 +65,16 @@ def test_pwd_chdir_entries def test_foreach ::Zip::File.open(TEST_ZIP) do |zf| - blockCalled = false + block_called = false assert_raises(Errno::ENOENT, 'No such file or directory - noSuchDir') do - zf.dir.foreach('noSuchDir') { |_e| blockCalled = true } + zf.dir.foreach('noSuchDir') { |_e| block_called = true } end - assert(!blockCalled) + assert(!block_called) assert_raises(Errno::ENOTDIR, 'Not a directory - file1') do - zf.dir.foreach('file1') { |_e| blockCalled = true } + zf.dir.foreach('file1') { |_e| block_called = true } end - assert(!blockCalled) + assert(!block_called) entries = [] zf.dir.foreach('.') { |e| entries << e } diff --git a/test/filesystem/file_nonmutating_test.rb b/test/filesystem/file_nonmutating_test.rb index cfe18ade..346d5a76 100644 --- a/test/filesystem/file_nonmutating_test.rb +++ b/test/filesystem/file_nonmutating_test.rb @@ -31,30 +31,30 @@ def test_exists? end def test_open_read - blockCalled = false + block_called = false @zip_file.file.open('file1', 'r') do |f| - blockCalled = true + block_called = true assert_equal("this is the entry 'file1' in my test archive!", f.readline.chomp) end - assert(blockCalled) + assert(block_called) - blockCalled = false + block_called = false @zip_file.file.open('file1', 'rb') do |f| # test binary flag is ignored - blockCalled = true + block_called = true assert_equal("this is the entry 'file1' in my test archive!", f.readline.chomp) end - assert(blockCalled) + assert(block_called) - blockCalled = false + block_called = false @zip_file.dir.chdir 'dir2' @zip_file.file.open('file21', 'r') do |f| - blockCalled = true + block_called = true assert_equal("this is the entry 'dir2/file21' in my test archive!", f.readline.chomp) end - assert(blockCalled) + assert(block_called) @zip_file.dir.chdir '/' assert_raises(Errno::ENOENT) do @@ -126,19 +126,19 @@ def test_file? include ExtraAssertions def test_dirname - assert_forwarded(File, :dirname, 'retVal', 'a/b/c/d') do + assert_forwarded(File, :dirname, 'ret_val', 'a/b/c/d') do @zip_file.file.dirname('a/b/c/d') end end def test_basename - assert_forwarded(File, :basename, 'retVal', 'a/b/c/d') do + assert_forwarded(File, :basename, 'ret_val', 'a/b/c/d') do @zip_file.file.basename('a/b/c/d') end end def test_split - assert_forwarded(File, :split, 'retVal', 'a/b/c/d') do + assert_forwarded(File, :split, 'ret_val', 'a/b/c/d') do @zip_file.file.split('a/b/c/d') end end @@ -246,21 +246,21 @@ def test_zero? assert(!@zip_file.file.zero?('notAFile')) assert(!@zip_file.file.zero?('file1')) assert(@zip_file.file.zero?('dir1')) - blockCalled = false + block_called = false ::Zip::File.open('test/data/generated/5entry.zip') do |zf| - blockCalled = true + block_called = true assert(zf.file.zero?('test/data/generated/empty.txt')) end - assert(blockCalled) + assert(block_called) assert(!@zip_file.file.stat('file1').zero?) assert(@zip_file.file.stat('dir1').zero?) - blockCalled = false + block_called = false ::Zip::File.open('test/data/generated/5entry.zip') do |zf| - blockCalled = true + block_called = true assert(zf.file.stat('test/data/generated/empty.txt').zero?) end - assert(blockCalled) + assert(block_called) end def test_expand_path diff --git a/test/gentestfiles.rb b/test/gentestfiles.rb index acdada45..503a0d00 100755 --- a/test/gentestfiles.rb +++ b/test/gentestfiles.rb @@ -84,18 +84,20 @@ def self.create_test_zips ::File.chmod(0o640, 'test/data/generated/empty_chmod640.txt') File.open('test/data/generated/short.txt', 'w') { |file| file << 'ABCDEF' } - ziptestTxt = '' - File.open('test/data/file2.txt') { |file| ziptestTxt = file.read } + test_text = '' + File.open('test/data/file2.txt') { |file| test_text = file.read } File.open('test/data/generated/longAscii.txt', 'w') do |file| - file << ziptestTxt while file.tell < 1E5 + file << test_text while file.tell < 1E5 end - testBinaryPattern = '' - File.open('test/data/generated/empty.zip') { |file| testBinaryPattern = file.read } - testBinaryPattern *= 4 + binary_pattern = '' + File.open('test/data/generated/empty.zip') do |file| + binary_pattern = file.read + end + binary_pattern *= 4 File.open('test/data/generated/longBinary.bin', 'wb') do |file| - file << testBinaryPattern << rand << "\0" while file.tell < 6E5 + file << binary_pattern << rand << "\0" while file.tell < 6E5 end raise "failed to create test zip '#{TEST_ZIP2.zip_name}'" \ diff --git a/test/ioextras/abstract_input_stream_test.rb b/test/ioextras/abstract_input_stream_test.rb index 4be0ba8d..a18c4e3d 100644 --- a/test/ioextras/abstract_input_stream_test.rb +++ b/test/ioextras/abstract_input_stream_test.rb @@ -14,13 +14,13 @@ class TestAbstractInputStream def initialize(string) super() @contents = string - @readPointer = 0 + @read_ptr = 0 end def sysread(chars_to_read, _buf = nil) - retVal = @contents[@readPointer, chars_to_read] - @readPointer += chars_to_read - retVal + ret_val = @contents[@read_ptr, chars_to_read] + @read_ptr += chars_to_read + ret_val end def produce_input @@ -28,7 +28,7 @@ def produce_input end def input_finished? - @contents[@readPointer].nil? + @contents[@read_ptr].nil? end end @@ -80,10 +80,10 @@ def test_gets_with_index end def test_each_line - lineNumber = 0 + line_num = 0 @io.each_line do |line| - assert_equal(TEST_LINES[lineNumber], line) - lineNumber += 1 + assert_equal(TEST_LINES[line_num], line) + line_num += 1 end end diff --git a/test/ioextras/abstract_output_stream_test.rb b/test/ioextras/abstract_output_stream_test.rb index 17b31a78..9b02309c 100644 --- a/test/ioextras/abstract_output_stream_test.rb +++ b/test/ioextras/abstract_output_stream_test.rb @@ -20,13 +20,13 @@ def <<(data) def setup @output_stream = TestOutputStream.new - @origCommaSep = $OUTPUT_FIELD_SEPARATOR - @origOutputSep = $OUTPUT_RECORD_SEPARATOR + @save_comma_sep = $OUTPUT_FIELD_SEPARATOR + @save_output_sep = $OUTPUT_RECORD_SEPARATOR end def teardown - $, = @origCommaSep - $\ = @origOutputSep + $, = @save_comma_sep + $\ = @save_output_sep end def test_write diff --git a/test/local_entry_test.rb b/test/local_entry_test.rb index ce634c89..58bcda74 100644 --- a/test/local_entry_test.rb +++ b/test/local_entry_test.rb @@ -41,56 +41,71 @@ def test_read_local_entry_from_non_zip_file end def test_read_local_entry_from_truncated_zip_file - zipFragment = '' - ::File.open(TestZipFile::TEST_ZIP2.zip_name) { |f| zipFragment = f.read(12) } # local header is at least 30 bytes - zipFragment.extend(IOizeString).reset + fragment = '' + # local header is at least 30 bytes + ::File.open(TestZipFile::TEST_ZIP2.zip_name) { |f| fragment = f.read(12) } + + fragment.extend(IOizeString).reset entry = ::Zip::Entry.new - entry.read_local_entry(zipFragment) + entry.read_local_entry(fragment) raise 'ZipError expected' rescue ::Zip::Error end def test_write_entry - entry = ::Zip::Entry.new('file.zip', 'entryName', 'my little comment', + entry = ::Zip::Entry.new('file.zip', 'entry_name', 'my little comment', 'thisIsSomeExtraInformation', 100, 987_654, ::Zip::Entry::DEFLATED, 400) write_to_file(LEH_FILE, CEH_FILE, entry) - entryReadLocal, entryReadCentral = read_from_file(LEH_FILE, CEH_FILE) - assert(entryReadCentral.extra['Zip64Placeholder'].nil?, 'zip64 placeholder should not be used in central directory') - compare_local_entry_headers(entry, entryReadLocal) - compare_c_dir_entry_headers(entry, entryReadCentral) + local_entry, central_entry = read_from_file(LEH_FILE, CEH_FILE) + assert( + central_entry.extra['Zip64Placeholder'].nil?, + 'zip64 placeholder should not be used in central directory' + ) + compare_local_entry_headers(entry, local_entry) + compare_c_dir_entry_headers(entry, central_entry) end def test_write_entry_with_zip64 ::Zip.write_zip64_support = true - entry = ::Zip::Entry.new('file.zip', 'entryName', 'my little comment', + entry = ::Zip::Entry.new('file.zip', 'entry_name', 'my little comment', 'thisIsSomeExtraInformation', 100, 987_654, ::Zip::Entry::DEFLATED, 400) + write_to_file(LEH_FILE, CEH_FILE, entry) - entryReadLocal, entryReadCentral = read_from_file(LEH_FILE, CEH_FILE) - assert(entryReadLocal.extra['Zip64Placeholder'], 'zip64 placeholder should be used in local file header') - entryReadLocal.extra.delete('Zip64Placeholder') # it was removed when writing the c_dir_entry, so remove from compare - assert(entryReadCentral.extra['Zip64Placeholder'].nil?, 'zip64 placeholder should not be used in central directory') - compare_local_entry_headers(entry, entryReadLocal) - compare_c_dir_entry_headers(entry, entryReadCentral) + local_entry, central_entry = read_from_file(LEH_FILE, CEH_FILE) + assert( + local_entry.extra['Zip64Placeholder'], + 'zip64 placeholder should be used in local file header' + ) + + # This was removed when writing the c_dir_entry, so remove from compare. + local_entry.extra.delete('Zip64Placeholder') + assert( + central_entry.extra['Zip64Placeholder'].nil?, + 'zip64 placeholder should not be used in central directory' + ) + + compare_local_entry_headers(entry, local_entry) + compare_c_dir_entry_headers(entry, central_entry) end def test_write_64entry ::Zip.write_zip64_support = true - entry = ::Zip::Entry.new('bigfile.zip', 'entryName', 'my little equine', + entry = ::Zip::Entry.new('bigfile.zip', 'entry_name', 'my little equine', 'malformed extra field because why not', 0x7766554433221100, 0xDEADBEEF, ::Zip::Entry::DEFLATED, 0x9988776655443322) write_to_file(LEH_FILE, CEH_FILE, entry) - entryReadLocal, entryReadCentral = read_from_file(LEH_FILE, CEH_FILE) - compare_local_entry_headers(entry, entryReadLocal) - compare_c_dir_entry_headers(entry, entryReadCentral) + local_entry, central_entry = read_from_file(LEH_FILE, CEH_FILE) + compare_local_entry_headers(entry, local_entry) + compare_c_dir_entry_headers(entry, central_entry) end def test_rewrite_local_header64 ::Zip.write_zip64_support = true buf1 = StringIO.new - entry = ::Zip::Entry.new('file.zip', 'entryName') + entry = ::Zip::Entry.new('file.zip', 'entry_name') entry.write_local_entry(buf1) assert(entry.extra['Zip64'].nil?, 'zip64 extra is unnecessarily present') @@ -104,7 +119,7 @@ def test_rewrite_local_header64 end def test_read_local_offset - entry = ::Zip::Entry.new('file.zip', 'entryName') + entry = ::Zip::Entry.new('file.zip', 'entry_name') entry.local_header_offset = 12_345 ::File.open(CEH_FILE, 'wb') { |f| entry.write_c_dir_entry(f) } read_entry = nil @@ -114,7 +129,7 @@ def test_read_local_offset def test_read64_local_offset ::Zip.write_zip64_support = true - entry = ::Zip::Entry.new('file.zip', 'entryName') + entry = ::Zip::Entry.new('file.zip', 'entry_name') entry.local_header_offset = 0x0123456789ABCDEF ::File.open(CEH_FILE, 'wb') { |f| entry.write_c_dir_entry(f) } read_entry = nil @@ -145,10 +160,17 @@ def write_to_file(local_filename, central_filename, entry) end def read_from_file(local_filename, central_filename) - localEntry = nil - cdirEntry = nil - ::File.open(local_filename, 'rb') { |f| localEntry = ::Zip::Entry.read_local_entry(f) } - ::File.open(central_filename, 'rb') { |f| cdirEntry = ::Zip::Entry.read_c_dir_entry(f) } - [localEntry, cdirEntry] + local_entry = nil + cdir_entry = nil + + ::File.open(local_filename, 'rb') do |f| + local_entry = ::Zip::Entry.read_local_entry(f) + end + + ::File.open(central_filename, 'rb') do |f| + cdir_entry = ::Zip::Entry.read_c_dir_entry(f) + end + + [local_entry, cdir_entry] end end diff --git a/test/settings_test.rb b/test/settings_test.rb index b79dc0ce..0510a6fc 100644 --- a/test/settings_test.rb +++ b/test/settings_test.rb @@ -54,15 +54,15 @@ def test_false_continue_on_exists_proc def test_true_continue_on_exists_proc Zip.continue_on_exists_proc = true - replacedEntry = nil + replaced_entry = nil ::Zip::File.open(TEST_ZIP.zip_name) do |zf| - replacedEntry = zf.entries.first.name - zf.add(replacedEntry, 'test/data/file2.txt') + replaced_entry = zf.entries.first.name + zf.add(replaced_entry, 'test/data/file2.txt') end ::Zip::File.open(TEST_ZIP.zip_name) do |zf| - assert_contains(zf, replacedEntry, 'test/data/file2.txt') + assert_contains(zf, replaced_entry, 'test/data/file2.txt') end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 12405456..598736e6 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -25,27 +25,27 @@ module IOizeString def read(count = nil) @tell ||= 0 count ||= size - retVal = slice(@tell, count) + ret_val = slice(@tell, count) @tell += count - retVal + ret_val end def seek(index, offset) @tell ||= 0 case offset when IO::SEEK_END - newPos = size + index + pos = size + index when IO::SEEK_SET - newPos = index + pos = index when IO::SEEK_CUR - newPos = @tell + index + pos = @tell + index else raise 'Error in test method IOizeString::seek' end - raise Errno::EINVAL if newPos < 0 || newPos >= size + raise Errno::EINVAL if pos < 0 || pos >= size - @tell = newPos + @tell = pos end def reset @@ -54,26 +54,26 @@ def reset end module DecompressorTests - # expects @refText, @refLines and @decompressor + # expects @ref_text, @ref_lines and @decompressor TEST_FILE = 'test/data/file1.txt' def setup - @refText = '' - File.open(TEST_FILE) { |f| @refText = f.read } - @refLines = @refText.split($INPUT_RECORD_SEPARATOR) + @ref_text = '' + File.open(TEST_FILE) { |f| @ref_text = f.read } + @ref_lines = @ref_text.split($INPUT_RECORD_SEPARATOR) end def test_read_everything - assert_equal(@refText, @decompressor.read) + assert_equal(@ref_text, @decompressor.read) end def test_read_in_chunks - chunkSize = 5 - while (decompressedChunk = @decompressor.read(chunkSize)) - assert_equal(@refText.slice!(0, chunkSize), decompressedChunk) + size = 5 + while (chunk = @decompressor.read(size)) + assert_equal(@ref_text.slice!(0, size), chunk) end - assert_equal(0, @refText.size) + assert_equal(0, @ref_text.size) end end @@ -93,9 +93,9 @@ def assert_entry_contents_for_stream(filename, zis, entry_name) actual = zis.read if expected != actual if (expected && actual) && (expected.length > 400 || actual.length > 400) - zipEntryFilename = entry_name + '.zipEntry' - File.open(zipEntryFilename, 'wb') { |entryfile| entryfile << actual } - raise("File '#{filename}' is different from '#{zipEntryFilename}'") + entry_filename = entry_name + '.zipEntry' + File.open(entry_filename, 'wb') { |entryfile| entryfile << actual } + raise("File '#{filename}' is different from '#{entry_filename}'") else assert_equal(expected, actual) end @@ -104,16 +104,16 @@ def assert_entry_contents_for_stream(filename, zis, entry_name) end def self.assert_contents(filename, string) - fileContents = '' - File.open(filename, 'rb') { |f| fileContents = f.read } - return unless fileContents != string - - if fileContents.length > 400 || string.length > 400 - stringFile = filename + '.other' - File.open(stringFile, 'wb') { |f| f << string } - raise("File '#{filename}' is different from contents of string stored in '#{stringFile}'") + contents = '' + File.open(filename, 'rb') { |f| contents = f.read } + return unless contents != string + + if contents.length > 400 || string.length > 400 + string_file = filename + '.other' + File.open(string_file, 'wb') { |f| f << string } + raise("File '#{filename}' is different from contents of string stored in '#{string_file}'") else - assert_equal(fileContents, string) + assert_equal(contents, string) end end @@ -157,9 +157,9 @@ def <<(data) def run_crc_test(compressor_class) str = "Here's a nice little text to compute the crc for! Ho hum, it is nice nice nice nice indeed." - fakeOut = TestOutputStream.new + fake_out = TestOutputStream.new - deflater = compressor_class.new(fakeOut) + deflater = compressor_class.new(fake_out) deflater << str assert_equal(0x919920fc, deflater.crc) end @@ -167,11 +167,11 @@ def run_crc_test(compressor_class) module Enumerable def compare_enumerables(enumerable) - otherAsArray = enumerable.to_a + array = enumerable.to_a each_with_index do |element, index| - return false unless yield(element, otherAsArray[index]) + return false unless yield(element, array[index]) end - size == otherAsArray.size + size == array.size end end @@ -191,18 +191,18 @@ def setup module ExtraAssertions def assert_forwarded(object, method, ret_val, *expected_args) - callArgs = nil - setCallArgsProc = proc { |args| callArgs = args } + call_args = nil + call_args_proc = proc { |args| call_args = args } object.instance_eval <<-END_EVAL, __FILE__, __LINE__ + 1 alias #{method}_org #{method} def #{method}(*args) - ObjectSpace._id2ref(#{setCallArgsProc.object_id}).call(args) + ObjectSpace._id2ref(#{call_args_proc.object_id}).call(args) ObjectSpace._id2ref(#{ret_val.object_id}) end END_EVAL assert_equal(ret_val, yield) # Invoke test - assert_equal(expected_args, callArgs) + assert_equal(expected_args, call_args) ensure object.instance_eval <<-END_EVAL, __FILE__, __LINE__ + 1 undef #{method} From f9b161eb32d6f63f7840838612c20f087f64da5f Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 19 Feb 2020 17:07:32 +0000 Subject: [PATCH 150/469] Fix Naming/VariableName cop in the library code. --- .rubocop_todo.yml | 7 ------- lib/zip/file.rb | 16 ++++++++-------- lib/zip/filesystem.rb | 12 ++++++------ 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 67bd0811..ae1fe055 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -38,13 +38,6 @@ Naming/AccessorMethodName: - 'lib/zip/streamable_stream.rb' - 'test/file_permissions_test.rb' -# Offense count: 721 -# Configuration parameters: EnforcedStyle. -# SupportedStyles: snake_case, camelCase -Naming/VariableName: - Exclude: - - 'lib/**/*.rb' - # Offense count: 7 # Configuration parameters: EnforcedStyle. # SupportedStyles: inline, group diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 0e7d4a90..999d9728 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -319,11 +319,11 @@ def remove(entry) # Renames the specified entry. def rename(entry, new_name, &continue_on_exists_proc) - foundEntry = get_entry(entry) + found_entry = get_entry(entry) check_entry_exists(new_name, continue_on_exists_proc, 'rename') - @entry_set.delete(foundEntry) - foundEntry.name = new_name - @entry_set << foundEntry + @entry_set.delete(found_entry) + found_entry.name = new_name + @entry_set << found_entry end # Replaces the specified entry with the contents of src_path (from @@ -420,15 +420,15 @@ def mkdir(entry_name, permission = 0o755) private def directory?(new_entry, src_path) - srcPathIsDirectory = ::File.directory?(src_path) - if new_entry.directory? && !srcPathIsDirectory + path_is_directory = ::File.directory?(src_path) + if new_entry.directory? && !path_is_directory raise ArgumentError, "entry name '#{new_entry}' indicates directory entry, but " \ "'#{src_path}' is not a directory" - elsif !new_entry.directory? && srcPathIsDirectory + elsif !new_entry.directory? && path_is_directory new_entry.name += '/' end - new_entry.directory? && srcPathIsDirectory + new_entry.directory? && path_is_directory end def check_entry_exists(entry_name, continue_on_exists_proc, proc_name) diff --git a/lib/zip/filesystem.rb b/lib/zip/filesystem.rb index 28ebad4d..d9928d4a 100644 --- a/lib/zip/filesystem.rb +++ b/lib/zip/filesystem.rb @@ -444,16 +444,16 @@ def new(directory_name) end def open(directory_name) - dirIt = new(directory_name) + dir_iter = new(directory_name) if block_given? begin - yield(dirIt) + yield(dir_iter) return nil ensure - dirIt.close + dir_iter.close end end - dirIt + dir_iter end def pwd @@ -487,9 +487,9 @@ def foreach(directory_name) path = @file.expand_path(directory_name) path << '/' unless path.end_with?('/') path = Regexp.escape(path) - subDirEntriesRegex = Regexp.new("^#{path}([^/]+)$") + subdir_entry_regex = Regexp.new("^#{path}([^/]+)$") @mapped_zip.each do |filename| - match = subDirEntriesRegex.match(filename) + match = subdir_entry_regex.match(filename) yield(match[1]) unless match.nil? end end From c30d9dfb265bcd1d7d11152e1ff626f5e22177c6 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 19 Feb 2020 17:54:03 +0000 Subject: [PATCH 151/469] Configure Naming/MemoizedInstanceVariableName in source. Rather than turn it off for a whole file, it's better to mark these exceptions in comments. --- .rubocop.yml | 6 ------ lib/zip/extra_field/old_unix.rb | 2 +- lib/zip/extra_field/unix.rb | 2 +- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 27712bd8..3fd8ffae 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -50,12 +50,6 @@ Metrics/MethodLength: Exclude: - 'test/**/*.rb' -# Rubocop confuses these as instances of "memoization". -Naming/MemoizedInstanceVariableName: - Exclude: - - 'lib/zip/extra_field/old_unix.rb' - - 'lib/zip/extra_field/unix.rb' - # Set a consistent way of checking types. Style/ClassCheck: EnforcedStyle: kind_of? diff --git a/lib/zip/extra_field/old_unix.rb b/lib/zip/extra_field/old_unix.rb index a9755b09..dfd2ba56 100644 --- a/lib/zip/extra_field/old_unix.rb +++ b/lib/zip/extra_field/old_unix.rb @@ -25,7 +25,7 @@ def merge(binstr) @uid ||= uid @gid ||= gid @atime ||= atime - @mtime ||= mtime + @mtime ||= mtime # rubocop:disable Naming/MemoizedInstanceVariableName end def ==(other) diff --git a/lib/zip/extra_field/unix.rb b/lib/zip/extra_field/unix.rb index 1bb70391..9a66c81d 100644 --- a/lib/zip/extra_field/unix.rb +++ b/lib/zip/extra_field/unix.rb @@ -21,7 +21,7 @@ def merge(binstr) uid, gid = content.unpack('vv') @uid ||= uid - @gid ||= gid + @gid ||= gid # rubocop:disable Naming/MemoizedInstanceVariableName end def ==(other) From ce17c57e2d0d0f8c61c535a7045cbe7c0fc44a4c Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 19 Feb 2020 17:59:22 +0000 Subject: [PATCH 152/469] Fix Naming/AccessorMethodName in the tests. This was kind of a misfire of this cop, but no bother to change. --- .rubocop_todo.yml | 1 - test/file_permissions_test.rb | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index ae1fe055..7776a745 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -36,7 +36,6 @@ Naming/AccessorMethodName: - 'lib/zip/filesystem.rb' - 'lib/zip/input_stream.rb' - 'lib/zip/streamable_stream.rb' - - 'test/file_permissions_test.rb' # Offense count: 7 # Configuration parameters: EnforcedStyle. diff --git a/test/file_permissions_test.rb b/test/file_permissions_test.rb index 4e4573a4..2d8283c9 100644 --- a/test/file_permissions_test.rb +++ b/test/file_permissions_test.rb @@ -15,7 +15,7 @@ def test_current_umask end def test_umask_000 - set_umask(0o000) do + apply_umask(0o000) do create_files end @@ -23,7 +23,7 @@ def test_umask_000 end def test_umask_066 - set_umask(0o066) do + apply_umask(0o066) do create_files end @@ -31,7 +31,7 @@ def test_umask_066 end def test_umask_027 - set_umask(0o027) do + apply_umask(0o027) do create_files end @@ -56,7 +56,7 @@ def create_files end # If anything goes wrong, make sure the umask is restored. - def set_umask(umask) + def apply_umask(umask) saved_umask = ::File.umask(umask) yield ensure From e33c07a6e757eed653b56b045b8a0ecff40c1533 Mon Sep 17 00:00:00 2001 From: Sebastian Henke Date: Mon, 2 Mar 2020 11:00:39 +0100 Subject: [PATCH 153/469] Use existing constant for ASCII_8BIT Co-Authored-By: John Lees-Miller --- test/output_stream_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/output_stream_test.rb b/test/output_stream_test.rb index e25fb84a..61c9ac37 100644 --- a/test/output_stream_test.rb +++ b/test/output_stream_test.rb @@ -40,7 +40,7 @@ def test_write_buffer_binmode zos.comment = TEST_ZIP.comment write_test_zip(zos) end - assert buffer.external_encoding.name === ASCII8BIT + assert_equal Encoding::ASCII_8BIT, buffer.external_encoding end def test_write_buffer_with_temp_file From 66324a711cc7311b9e022bfc0badcbbaebc7308e Mon Sep 17 00:00:00 2001 From: Sebastian Henke Date: Mon, 2 Mar 2020 11:06:50 +0100 Subject: [PATCH 154/469] Remove duplicate binmode call --- lib/zip/file.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 5aafe2e1..c768d07d 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -362,7 +362,6 @@ def commit # Write buffer write changes to buffer and return def write_buffer(io = ::StringIO.new('')) - io.binmode if io.respond_to?(:binmode) ::Zip::OutputStream.write_buffer(io) do |zos| @entry_set.each { |e| e.write_to_zip_output_stream(zos) } zos.comment = comment From 4c789c28212f38216a88982ce52f0992b1853805 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Sat, 14 Mar 2020 11:00:39 +0000 Subject: [PATCH 155/469] Remove unused constant from #439 --- test/output_stream_test.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/output_stream_test.rb b/test/output_stream_test.rb index 739d3ee8..b2f64ab9 100644 --- a/test/output_stream_test.rb +++ b/test/output_stream_test.rb @@ -6,8 +6,6 @@ class ZipOutputStreamTest < MiniTest::Test TEST_ZIP = TestZipFile::TEST_ZIP2.clone TEST_ZIP.zip_name = 'test/data/generated/output.zip' - ASCII8BIT = 'ASCII-8BIT' - def test_new zos = ::Zip::OutputStream.new(TEST_ZIP.zip_name) zos.comment = TEST_ZIP.comment From a64a14767dd458f8da6107721a428aa5e2b3f5c9 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Sat, 14 Mar 2020 11:09:33 +0000 Subject: [PATCH 156/469] Bump rake version (development dependency) --- rubyzip.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rubyzip.gemspec b/rubyzip.gemspec index 1707ef5d..2e7cbf78 100644 --- a/rubyzip.gemspec +++ b/rubyzip.gemspec @@ -24,6 +24,6 @@ Gem::Specification.new do |s| s.add_development_dependency 'coveralls', '~> 0.7' s.add_development_dependency 'minitest', '~> 5.4' s.add_development_dependency 'pry', '~> 0.10' - s.add_development_dependency 'rake', '~> 10.3' + s.add_development_dependency 'rake', '~> 12.3', '>= 12.3.3' s.add_development_dependency 'rubocop', '~> 0.79' end From 516941bec56fbceaed8e75887247b74b97cbf341 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Sat, 14 Mar 2020 11:28:02 +0000 Subject: [PATCH 157/469] Update changelog for #439 and #440 --- Changelog.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Changelog.md b/Changelog.md index 6ec9a705..1ea1bb0b 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,10 +1,13 @@ # X.X.X (Next) - Fix frozen string literal error [#431](https://github.com/rubyzip/rubyzip/pull/431) -- Upgrade rubocop and fix various linting complaints [#437](https://github.com/rubyzip/rubyzip/pull/437) +- Set `OutputStream.write_buffer`'s buffer to binmode [#439](https://github.com/rubyzip/rubyzip/pull/439) +- Upgrade rubocop and fix various linting complaints [#437](https://github.com/rubyzip/rubyzip/pull/437) [#440](https://github.com/rubyzip/rubyzip/pull/440) Tooling: + - Add a `bin/console` script for development [#420](https://github.com/rubyzip/rubyzip/pull/420) +- Update rake requirement (development dependency only) to fix a security alert. # 2.2.0 (2020-02-01) @@ -13,10 +16,10 @@ Tooling: # 2.1.0 (2020-01-25) - Fix (at least partially) the `restore_times` and `restore_permissions` options to `Zip::File.new` [#413](https://github.com/rubyzip/rubyzip/pull/413) - - Previously, neither option did anything, regardless of what it was set to. We have therefore defaulted them to `false` to preserve the current behavior, for the time being. If you have explicitly set either to `true`, it will now have an effect. - - Fix handling of UniversalTime (`mtime`, `atime`, `ctime`) fields. [#421](https://github.com/rubyzip/rubyzip/pull/421) - - Previously, `Zip::File` did not pass the options to `Zip::Entry` in some cases. [#423](https://github.com/rubyzip/rubyzip/pull/423) - - Note that `restore_times` in this release does nothing on Windows and only restores `mtime`, not `atime` or `ctime`. + - Previously, neither option did anything, regardless of what it was set to. We have therefore defaulted them to `false` to preserve the current behavior, for the time being. If you have explicitly set either to `true`, it will now have an effect. + - Fix handling of UniversalTime (`mtime`, `atime`, `ctime`) fields. [#421](https://github.com/rubyzip/rubyzip/pull/421) + - Previously, `Zip::File` did not pass the options to `Zip::Entry` in some cases. [#423](https://github.com/rubyzip/rubyzip/pull/423) + - Note that `restore_times` in this release does nothing on Windows and only restores `mtime`, not `atime` or `ctime`. - Allow `Zip::File.open` to take an options hash like `Zip::File.new` [#418](https://github.com/rubyzip/rubyzip/pull/418) - Always print warnings with `warn`, instead of a mix of `puts` and `warn` [#416](https://github.com/rubyzip/rubyzip/pull/416) - Create temporary files in the system temporary directory instead of the directory of the zip file [#411](https://github.com/rubyzip/rubyzip/pull/411) @@ -31,7 +34,7 @@ Tooling Security - Default the `validate_entry_sizes` option to `true`, so that callers can trust an entry's reported size when using `extract` [#403](https://github.com/rubyzip/rubyzip/pull/403) - - This option defaulted to `false` in 1.3.0 for backward compatibility, but it now defaults to `true`. If you are using an older version of ruby and can't yet upgrade to 2.x, you can still use 1.3.0 and set the option to `true`. + - This option defaulted to `false` in 1.3.0 for backward compatibility, but it now defaults to `true`. If you are using an older version of ruby and can't yet upgrade to 2.x, you can still use 1.3.0 and set the option to `true`. Tooling / Documentation @@ -43,7 +46,7 @@ Tooling / Documentation Security - Add `validate_entry_sizes` option so that callers can trust an entry's reported size when using `extract` [#403](https://github.com/rubyzip/rubyzip/pull/403) - - This option defaults to `false` for backward compatibility in this release, but you are strongly encouraged to set it to `true`. It will default to `true` in rubyzip 2.0. + - This option defaults to `false` for backward compatibility in this release, but you are strongly encouraged to set it to `true`. It will default to `true` in rubyzip 2.0. New Feature From 69186f65cdaa69a46e32ab81661376d648f61566 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Sat, 14 Mar 2020 11:31:39 +0000 Subject: [PATCH 158/469] Bump version to 2.3.0 --- Changelog.md | 2 ++ lib/zip/version.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 1ea1bb0b..5131d210 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,7 @@ # X.X.X (Next) +# 2.3.0 (2020-03-14) + - Fix frozen string literal error [#431](https://github.com/rubyzip/rubyzip/pull/431) - Set `OutputStream.write_buffer`'s buffer to binmode [#439](https://github.com/rubyzip/rubyzip/pull/439) - Upgrade rubocop and fix various linting complaints [#437](https://github.com/rubyzip/rubyzip/pull/437) [#440](https://github.com/rubyzip/rubyzip/pull/440) diff --git a/lib/zip/version.rb b/lib/zip/version.rb index 0955ae03..0b20c214 100644 --- a/lib/zip/version.rb +++ b/lib/zip/version.rb @@ -1,3 +1,3 @@ module Zip - VERSION = '2.2.0' + VERSION = '2.3.0' end From f37e679e7caa111ccc7f29c6b7fc6bf99b263f23 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Thu, 20 Feb 2020 14:58:32 +0000 Subject: [PATCH 159/469] Add a rubocop job to the Travis config. --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index b903c3b3..969d4c0e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,10 @@ matrix: - rvm: jruby-head jdk: openjdk11 - rvm: rbx-4 + - name: Rubocop + script: + - bundle info rubocop + - bundle exec rubocop allow_failures: - rvm: ruby-head - rvm: rbx-4 From ee06a8038aac41ba6128f658b7c9ddc3130d4fe3 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Thu, 20 Feb 2020 15:26:18 +0000 Subject: [PATCH 160/469] Bump Rubocop version and lock it down at patch level. Now we're using Rubocop in CI we need to prevent CI from dragging in a newer version than that which we are expecting. This will avoid unexpected fails from the linting stage. --- .rubocop.yml | 6 ++++++ .rubocop_todo.yml | 4 ++++ rubyzip.gemspec | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.rubocop.yml b/.rubocop.yml index 3fd8ffae..ef285c5e 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -54,6 +54,12 @@ Metrics/MethodLength: Style/ClassCheck: EnforcedStyle: kind_of? +Style/HashEachMethods: + Enabled: true + +Style/HashTransformValues: + Enabled: true + # Allow this multi-line block chain as it actually reads better # than the alternatives. Style/MultilineBlockChain: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 7776a745..1debf8fe 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -77,6 +77,10 @@ Style/FormatStringToken: Style/FrozenStringLiteralComment: Enabled: false +# This one has to be off until our base ruby is at least 2.5. +Style/HashTransformKeys: + Enabled: false + # Offense count: 17 # Cop supports --auto-correct. Style/IfUnlessModifier: diff --git a/rubyzip.gemspec b/rubyzip.gemspec index 2e7cbf78..24e9ecf3 100644 --- a/rubyzip.gemspec +++ b/rubyzip.gemspec @@ -25,5 +25,5 @@ Gem::Specification.new do |s| s.add_development_dependency 'minitest', '~> 5.4' s.add_development_dependency 'pry', '~> 0.10' s.add_development_dependency 'rake', '~> 12.3', '>= 12.3.3' - s.add_development_dependency 'rubocop', '~> 0.79' + s.add_development_dependency 'rubocop', '~> 0.80.1' end From 0c34cc18144784f16e79764ce9a649759e200f5f Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Thu, 20 Feb 2020 15:42:39 +0000 Subject: [PATCH 161/469] Explicitly set Rubocop ruby version to 2.4. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 969d4c0e..a140064c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,7 @@ matrix: jdk: openjdk11 - rvm: rbx-4 - name: Rubocop + rvm: 2.4 script: - bundle info rubocop - bundle exec rubocop From 2f311541dafea15573fa20487ec6db7c4f0fdb42 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 14 Mar 2020 17:54:08 +0000 Subject: [PATCH 162/469] Update the auto-config for Rubocop 0.80. --- .rubocop_todo.yml | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 1debf8fe..831139d6 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,35 +1,35 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2020-02-08 14:58:51 +0000 using RuboCop version 0.79.0. +# on 2020-03-14 17:50:29 +0000 using RuboCop version 0.80.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 15 +# Offense count: 5 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 580 + Max: 570 # Offense count: 26 Metrics/CyclomaticComplexity: Max: 14 -# Offense count: 120 +# Offense count: 44 # Configuration parameters: CountComments, ExcludedMethods. Metrics/MethodLength: - Max: 30 + Max: 29 # Offense count: 2 # Configuration parameters: CountKeywordArgs. Metrics/ParameterLists: Max: 10 -# Offense count: 21 +# Offense count: 20 Metrics/PerceivedComplexity: Max: 15 -# Offense count: 9 +# Offense count: 8 Naming/AccessorMethodName: Exclude: - 'lib/zip/entry.rb' @@ -60,7 +60,7 @@ Style/ClassAndModuleChildren: - 'lib/zip/extra_field/zip64.rb' - 'lib/zip/extra_field/zip64_placeholder.rb' -# Offense count: 26 +# Offense count: 25 Style/Documentation: Enabled: false @@ -70,10 +70,10 @@ Style/Documentation: Style/FormatStringToken: EnforcedStyle: unannotated -# Offense count: 95 +# Offense count: 96 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. -# SupportedStyles: always, never +# SupportedStyles: always, always_true, never Style/FrozenStringLiteralComment: Enabled: false @@ -81,15 +81,13 @@ Style/FrozenStringLiteralComment: Style/HashTransformKeys: Enabled: false -# Offense count: 17 +# Offense count: 13 # Cop supports --auto-correct. Style/IfUnlessModifier: Exclude: - 'lib/zip/entry.rb' - - 'lib/zip/extra_field/generic.rb' - 'lib/zip/file.rb' - 'lib/zip/filesystem.rb' - - 'lib/zip/input_stream.rb' - 'lib/zip/pass_thru_decompressor.rb' - 'lib/zip/streamable_stream.rb' From 382cc84915e9a4a5fff7e85b3999910d81a0c359 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Sat, 4 Apr 2020 10:11:06 +0100 Subject: [PATCH 163/469] Update changelog for #444 --- Changelog.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Changelog.md b/Changelog.md index 5131d210..10fc371d 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,9 @@ # X.X.X (Next) +Tooling: + +- Update rubocop again and run it in CI. [#444](https://github.com/rubyzip/rubyzip/pull/444) + # 2.3.0 (2020-03-14) - Fix frozen string literal error [#431](https://github.com/rubyzip/rubyzip/pull/431) From 262ba001a4a0cd2b32956b1a4a597ce7c32c85e7 Mon Sep 17 00:00:00 2001 From: Lucas Kanashiro Date: Wed, 25 Mar 2020 14:45:09 -0300 Subject: [PATCH 164/469] test/file_extract_test.rb: fix test_extract_incorrect_size in s390x arch Using the current pack directives makes test_extract_incorrect_size fail in s390x architecture because of the endian probably. This is the output of the command executed by the test in amd64: irb(main):001:0> [501, 500000, 1].pack('LLS') => "\xF5\x01\x00\x00 \xA1\a\x00\x01\x00" And the output of the same command in s390x: irb(main):001:0> [501, 500000, 1].pack('LLS') => "\x00\x00\x01\xF5\x00\a\xA1 \x00\x01" Changing to 'VVv' pack directives like is used in lib/zib/entry.rb fixes the test in s390x and does not add a regression in amd64. --- test/file_extract_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/file_extract_test.rb b/test/file_extract_test.rb index 0e697187..d8306b35 100644 --- a/test/file_extract_test.rb +++ b/test/file_extract_test.rb @@ -110,8 +110,8 @@ def test_extract_incorrect_size assert_equal true_size, a_entry.size end - true_size_bytes = [compressed_size, true_size, file_name.size].pack('LLS') - fake_size_bytes = [compressed_size, fake_size, file_name.size].pack('LLS') + true_size_bytes = [compressed_size, true_size, file_name.size].pack('VVv') + fake_size_bytes = [compressed_size, fake_size, file_name.size].pack('VVv') data = File.binread(real_zip) assert data.include?(true_size_bytes) From b653d57635cc5da067b24737ce9f6ad84ce47799 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Sat, 11 Apr 2020 10:37:05 +0100 Subject: [PATCH 165/469] Update changelog for #445 --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index 10fc371d..cb4a893e 100644 --- a/Changelog.md +++ b/Changelog.md @@ -3,6 +3,7 @@ Tooling: - Update rubocop again and run it in CI. [#444](https://github.com/rubyzip/rubyzip/pull/444) +- Fix a test that was incorrect on big-endian architectures. [#445](https://github.com/rubyzip/rubyzip/pull/445) # 2.3.0 (2020-03-14) From 5af76cecb51e618ded61bba7a9e4078b4df7b921 Mon Sep 17 00:00:00 2001 From: Joni Lahtinen Date: Wed, 29 Apr 2020 16:38:33 +0300 Subject: [PATCH 166/469] Use Zlib::SYNC_FLUSH so buffer does not grow until finished With JRuby implementation deflate would always return empty string without Zlib::SYNC_FLUSH. That can cause memory problems when large files are in deflate buffer as whole and red there with finish call at once. --- lib/zip/deflater.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/zip/deflater.rb b/lib/zip/deflater.rb index 8509cf47..b197f5ca 100644 --- a/lib/zip/deflater.rb +++ b/lib/zip/deflater.rb @@ -13,7 +13,7 @@ def <<(data) val = data.to_s @crc = Zlib.crc32(val, @crc) @size += val.bytesize - buffer = @zlib_deflater.deflate(data) + buffer = @zlib_deflater.deflate(data, Zlib::SYNC_FLUSH) if buffer.empty? @output_stream else @@ -22,7 +22,9 @@ def <<(data) end def finish - @output_stream << @encrypter.encrypt(@zlib_deflater.finish) until @zlib_deflater.finished? + buffer = @zlib_deflater.finish + @output_stream << @encrypter.encrypt(buffer) unless buffer.empty? + @zlib_deflater.close end attr_reader :size, :crc From 99d8f59eaf8baebf971fb603b437d24a26d93c3a Mon Sep 17 00:00:00 2001 From: Joni Lahtinen Date: Wed, 29 Apr 2020 16:41:21 +0300 Subject: [PATCH 167/469] Change encryption_test less implementation specific Seems like zlib deflate compress differently when Zlib::SYNC_FLUSH is used. --- test/encryption_test.rb | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/test/encryption_test.rb b/test/encryption_test.rb index d3ed5ffb..d06c4712 100644 --- a/test/encryption_test.rb +++ b/test/encryption_test.rb @@ -14,20 +14,28 @@ def teardown end def test_encrypt - test_file = ::File.open(ENCRYPT_ZIP_TEST_FILE, 'rb').read - - @rand = [250, 143, 107, 13, 143, 22, 155, 75, 228, 150, 12] - @output = ::Zip::DOSTime.stub(:now, ::Zip::DOSTime.new(2014, 12, 17, 15, 56, 24)) do - Random.stub(:rand, ->(_range) { @rand.shift }) do - Zip::OutputStream.write_buffer(::StringIO.new(''), Zip::TraditionalEncrypter.new('password')) do |zos| - zos.put_next_entry('file1.txt') - zos.write ::File.open(INPUT_FILE1).read - end.string - end + content = File.open(INPUT_FILE1, 'r').read + test_filename = 'top_secret_file.txt' + + password = 'swordfish' + + encrypted_zip = Zip::OutputStream.write_buffer(::StringIO.new(''), Zip::TraditionalEncrypter.new(password)) do |out| + out.put_next_entry(test_filename) + out.write content + end + + Zip::InputStream.open(encrypted_zip, 0, Zip::TraditionalDecrypter.new(password)) do |zis| + entry = zis.get_next_entry + assert_equal test_filename, entry.name + assert_equal 1327, entry.size + assert_equal content, zis.read end - @output.unpack('C*').each_with_index do |c, i| - assert_equal test_file[i].ord, c + assert_raises(Zip::DecompressionError) do + Zip::InputStream.open(encrypted_zip, 0, Zip::TraditionalDecrypter.new(password + 'wrong')) do |zis| + zis.get_next_entry + assert_equal content, zis.read + end end end From 0fee529de577ab81c0cce9dd6f12e97db07ccc28 Mon Sep 17 00:00:00 2001 From: Joni Lahtinen Date: Wed, 29 Apr 2020 17:06:29 +0300 Subject: [PATCH 168/469] Use guard instead of if else construct --- lib/zip/deflater.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/zip/deflater.rb b/lib/zip/deflater.rb index b197f5ca..9f5371c8 100644 --- a/lib/zip/deflater.rb +++ b/lib/zip/deflater.rb @@ -14,11 +14,9 @@ def <<(data) @crc = Zlib.crc32(val, @crc) @size += val.bytesize buffer = @zlib_deflater.deflate(data, Zlib::SYNC_FLUSH) - if buffer.empty? - @output_stream - else - @output_stream << @encrypter.encrypt(buffer) - end + return @output_stream if buffer.empty? + + @output_stream << @encrypter.encrypt(buffer) end def finish From cf73152560ec10c5c2cfd43afe82e996ef13006a Mon Sep 17 00:00:00 2001 From: Igor Victor Date: Thu, 13 Aug 2020 17:37:09 +0100 Subject: [PATCH 169/469] enable truffle ruby in Travis CI @eregon enabling truffle ruby support for rubyzip --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index a140064c..953a930b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ rvm: - 2.6 - 2.7 - ruby-head + - truffleruby-head matrix: fast_finish: true include: From e4f6d5ec56c45e17521516c517cfe60a4fdb32ef Mon Sep 17 00:00:00 2001 From: Igor Victor Date: Mon, 24 Aug 2020 13:19:53 +0200 Subject: [PATCH 170/469] Allow failures for truffleruby-head and truffleruby --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 953a930b..c73a251b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ rvm: - 2.7 - ruby-head - truffleruby-head + - truffleruby matrix: fast_finish: true include: @@ -27,6 +28,8 @@ matrix: - rvm: ruby-head - rvm: rbx-4 - rvm: jruby-head + - rvm: truffleruby-head + - rvm: truffleruby before_install: - gem --version before_script: From 0790695e8c3f10120143e8e1d4305f5a37032fcd Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 3 May 2020 11:34:47 +0100 Subject: [PATCH 171/469] Clean up OutputStream#init_next_entry. There's no need for this private method to specify a default. --- lib/zip/output_stream.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zip/output_stream.rb b/lib/zip/output_stream.rb index 266083cd..f46f3271 100644 --- a/lib/zip/output_stream.rb +++ b/lib/zip/output_stream.rb @@ -142,7 +142,7 @@ def finalize_current_entry @compressor = ::Zip::NullCompressor.instance end - def init_next_entry(entry, level = Zip.default_compression) + def init_next_entry(entry, level) finalize_current_entry @entry_set << entry entry.write_local_entry(@output_stream) From ef520b4b946f232b09fa8d1ae0db02899016c714 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 3 May 2020 14:54:19 +0100 Subject: [PATCH 172/469] Add `compression_level` to the Entry API. Allow an Entry to specify a compression level and pass this down to the underlying OutputStream infrastructure. OutputStream has been able to specify a compression level for a while but this has, up until now, only ever been set to the default. This fundamentally changes the API so will need a major version bump. --- .rubocop_todo.yml | 2 +- lib/zip/entry.rb | 12 +++++++++--- lib/zip/file.rb | 2 +- lib/zip/output_stream.rb | 1 + test/entry_test.rb | 17 +++++++++-------- test/file_test.rb | 4 ++-- test/local_entry_test.rb | 3 ++- test/output_stream_test.rb | 2 +- test/test_helper.rb | 1 + 9 files changed, 27 insertions(+), 17 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 831139d6..ae0c7049 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -9,7 +9,7 @@ # Offense count: 5 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 570 + Max: 580 # Offense count: 26 Metrics/CyclomaticComplexity: diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index a67c6568..e2160432 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -14,6 +14,7 @@ class Entry :unix_uid, :unix_gid, :unix_perms, :dirty attr_reader :ftype, :filepath # :nodoc: + attr_writer :compression_level # :nodoc: def set_default_vars_values @local_header_offset = 0 @@ -66,8 +67,9 @@ def initialize(*args) @compressed_size = args[4] || 0 @crc = args[5] || 0 @compression_method = args[6] || ::Zip::Entry::DEFLATED - @size = args[7] || 0 - @time = args[8] || ::Zip::DOSTime.now + @compression_level = args[7] || ::Zip.default_compression + @size = args[8] || 0 + @time = args[9] || ::Zip::DOSTime.now @ftype = name_is_directory? ? :directory : :file @extra = ::Zip::ExtraField.new(@extra.to_s) unless @extra.kind_of?(::Zip::ExtraField) @@ -579,7 +581,11 @@ def write_to_zip_output_stream(zip_output_stream) #:nodoc:all if @ftype == :directory zip_output_stream.put_next_entry(self, nil, nil, ::Zip::Entry::STORED) elsif @filepath - zip_output_stream.put_next_entry(self, nil, nil, compression_method || ::Zip::Entry::DEFLATED) + zip_output_stream.put_next_entry( + self, nil, nil, + compression_method || ::Zip::Entry::DEFLATED, + @compression_level || ::Zip.default_compression + ) get_input_stream { |is| ::Zip::IOExtras.copy_stream(zip_output_stream, is) } else zip_output_stream.copy_raw_entry(self) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 999d9728..f5bae24a 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -273,7 +273,7 @@ def get_output_stream(entry, permission_int = nil, comment = nil, if entry.kind_of?(Entry) entry else - Entry.new(@name, entry.to_s, comment, extra, compressed_size, crc, compression_method, size, time) + Entry.new(@name, entry.to_s, comment, extra, compressed_size, crc, compression_method, ::Zip.default_compression, size, time) end if new_entry.directory? raise ArgumentError, diff --git a/lib/zip/output_stream.rb b/lib/zip/output_stream.rb index f46f3271..4168e60b 100644 --- a/lib/zip/output_stream.rb +++ b/lib/zip/output_stream.rb @@ -102,6 +102,7 @@ def put_next_entry(entry_name, comment = nil, extra = nil, compression_method = new_entry.extra = extra.kind_of?(ExtraField) ? extra : ExtraField.new(extra.to_s) end new_entry.compression_method = compression_method unless compression_method.nil? + new_entry.compression_level = level unless level.nil? init_next_entry(new_entry, level) @current_entry = new_entry end diff --git a/test/entry_test.rb b/test/entry_test.rb index 8daf7adc..541ba41f 100644 --- a/test/entry_test.rb +++ b/test/entry_test.rb @@ -11,6 +11,7 @@ def test_constructor_and_getters TEST_COMPRESSED_SIZE, TEST_CRC, TEST_COMPRESSIONMETHOD, + TEST_COMPRESSIONLEVEL, TEST_SIZE, TEST_TIME) @@ -41,28 +42,28 @@ def test_is_directory_and_is_file def test_equality entry1 = ::Zip::Entry.new('file.zip', 'name', 'isNotCompared', 'something extra', 123, 1234, - ::Zip::Entry::DEFLATED, 10_000) + ::Zip::Entry::DEFLATED, ::Zip.default_compression, 10_000) entry2 = ::Zip::Entry.new('file.zip', 'name', 'isNotComparedXXX', 'something extra', 123, 1234, - ::Zip::Entry::DEFLATED, 10_000) + ::Zip::Entry::DEFLATED, ::Zip.default_compression, 10_000) entry3 = ::Zip::Entry.new('file.zip', 'name2', 'isNotComparedXXX', 'something extra', 123, 1234, - ::Zip::Entry::DEFLATED, 10_000) + ::Zip::Entry::DEFLATED, ::Zip.default_compression, 10_000) entry4 = ::Zip::Entry.new('file.zip', 'name2', 'isNotComparedXXX', 'something extraXX', 123, 1234, - ::Zip::Entry::DEFLATED, 10_000) + ::Zip::Entry::DEFLATED, ::Zip.default_compression, 10_000) entry5 = ::Zip::Entry.new('file.zip', 'name2', 'isNotComparedXXX', 'something extraXX', 12, 1234, - ::Zip::Entry::DEFLATED, 10_000) + ::Zip::Entry::DEFLATED, ::Zip.default_compression, 10_000) entry6 = ::Zip::Entry.new('file.zip', 'name2', 'isNotComparedXXX', 'something extraXX', 12, 123, - ::Zip::Entry::DEFLATED, 10_000) + ::Zip::Entry::DEFLATED, ::Zip.default_compression, 10_000) entry7 = ::Zip::Entry.new('file.zip', 'name2', 'isNotComparedXXX', 'something extraXX', 12, 123, - ::Zip::Entry::STORED, 10_000) + ::Zip::Entry::STORED, ::Zip.default_compression, 10_000) entry8 = ::Zip::Entry.new('file.zip', 'name2', 'isNotComparedXXX', 'something extraXX', 12, 123, - ::Zip::Entry::STORED, 100_000) + ::Zip::Entry::STORED, ::Zip.default_compression, 100_000) assert_equal(entry1, entry1) assert_equal(entry1, entry2) diff --git a/test/file_test.rb b/test/file_test.rb index c11af675..8a5dd432 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -87,8 +87,8 @@ def test_get_output_stream assert_equal(custom_entry_args[2], entry.compressed_size) assert_equal(custom_entry_args[3], entry.crc) assert_equal(custom_entry_args[4], entry.compression_method) - assert_equal(custom_entry_args[5], entry.size) - assert_equal(custom_entry_args[6], entry.time) + assert_equal(custom_entry_args[6], entry.size) + assert_equal(custom_entry_args[7], entry.time) zf.get_output_stream('entry.bin') do |os| os.write(::File.open('test/data/generated/5entry.zip', 'rb').read) diff --git a/test/local_entry_test.rb b/test/local_entry_test.rb index 58bcda74..28148039 100644 --- a/test/local_entry_test.rb +++ b/test/local_entry_test.rb @@ -94,7 +94,8 @@ def test_write_64entry ::Zip.write_zip64_support = true entry = ::Zip::Entry.new('bigfile.zip', 'entry_name', 'my little equine', 'malformed extra field because why not', - 0x7766554433221100, 0xDEADBEEF, ::Zip::Entry::DEFLATED, + 0x7766554433221100, 0xDEADBEEF, + ::Zip::Entry::DEFLATED, ::Zip.default_compression, 0x9988776655443322) write_to_file(LEH_FILE, CEH_FILE, entry) local_entry, central_entry = read_from_file(LEH_FILE, CEH_FILE) diff --git a/test/output_stream_test.rb b/test/output_stream_test.rb index b2f64ab9..79fb8f71 100644 --- a/test/output_stream_test.rb +++ b/test/output_stream_test.rb @@ -92,7 +92,7 @@ def test_put_next_entry def test_put_next_entry_using_zip_entry_creates_entries_with_correct_timestamps file = ::File.open('test/data/file2.txt', 'rb') ::Zip::OutputStream.open(TEST_ZIP.zip_name) do |zos| - zip_entry = ::Zip::Entry.new(zos, file.path, '', '', 0, 0, ::Zip::Entry::DEFLATED, 0, ::Zip::DOSTime.at(file.mtime)) + zip_entry = ::Zip::Entry.new(zos, file.path, '', '', 0, 0, ::Zip::Entry::DEFLATED, ::Zip.default_compression, 0, ::Zip::DOSTime.at(file.mtime)) zos.put_next_entry(zip_entry) zos << file.read end diff --git a/test/test_helper.rb b/test/test_helper.rb index 598736e6..973b4abd 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -218,6 +218,7 @@ module ZipEntryData TEST_CRC = 325_324 TEST_EXTRA = 'Some data here' TEST_COMPRESSIONMETHOD = ::Zip::Entry::DEFLATED + TEST_COMPRESSIONLEVEL = ::Zip.default_compression TEST_NAME = 'entry name' TEST_SIZE = 8432 TEST_ISDIRECTORY = false From 14451e63e70058242d8cd5680eb15c8777104d2c Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 24 May 2020 11:52:41 +0100 Subject: [PATCH 173/469] Add setting a compression level to the File options. It looks like it needs to be surfaced in `add` and `get_output_stream`. The compression level defaults to whatever the global default is unless it is overridden on opening the Zip::File. Also needed to reorder some of the requires in the top-level module file now that we are using defaults in the File class. --- .rubocop_todo.yml | 4 +-- lib/zip/file.rb | 17 +++++++--- test/file_test.rb | 81 +++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 92 insertions(+), 10 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index ae0c7049..04879430 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -18,12 +18,12 @@ Metrics/CyclomaticComplexity: # Offense count: 44 # Configuration parameters: CountComments, ExcludedMethods. Metrics/MethodLength: - Max: 29 + Max: 32 # Offense count: 2 # Configuration parameters: CountKeywordArgs. Metrics/ParameterLists: - Max: 10 + Max: 12 # Offense count: 20 Metrics/PerceivedComplexity: diff --git a/lib/zip/file.rb b/lib/zip/file.rb index f5bae24a..600e6160 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -75,7 +75,9 @@ class File < CentralDirectory # a new archive if it doesn't exist already. def initialize(path_or_io, create = false, buffer = false, options = {}) super() - options = DEFAULT_OPTIONS.merge(options) + options = DEFAULT_OPTIONS + .merge(compression_level: ::Zip.default_compression) + .merge(options) @name = path_or_io.respond_to?(:path) ? path_or_io.path : path_or_io @comment = '' @create = create ? true : false # allow any truthy value to mean true @@ -111,6 +113,7 @@ def initialize(path_or_io, create = false, buffer = false, options = {}) @restore_ownership = options[:restore_ownership] @restore_permissions = options[:restore_permissions] @restore_times = options[:restore_times] + @compression_level = options[:compression_level] end class << self @@ -266,14 +269,14 @@ def get_input_stream(entry, &a_proc) # File.open method. def get_output_stream(entry, permission_int = nil, comment = nil, extra = nil, compressed_size = nil, crc = nil, - compression_method = nil, size = nil, time = nil, - &a_proc) + compression_method = nil, compression_level = nil, + size = nil, time = nil, &a_proc) new_entry = if entry.kind_of?(Entry) entry else - Entry.new(@name, entry.to_s, comment, extra, compressed_size, crc, compression_method, ::Zip.default_compression, size, time) + Entry.new(@name, entry.to_s, comment, extra, compressed_size, crc, compression_method, compression_level, size, time) end if new_entry.directory? raise ArgumentError, @@ -299,7 +302,11 @@ def read(entry) def add(entry, src_path, &continue_on_exists_proc) continue_on_exists_proc ||= proc { ::Zip.continue_on_exists_proc } check_entry_exists(entry, continue_on_exists_proc, 'add') - new_entry = entry.kind_of?(::Zip::Entry) ? entry : ::Zip::Entry.new(@name, entry.to_s) + new_entry = if entry.kind_of?(::Zip::Entry) + entry + else + ::Zip::Entry.new(@name, entry.to_s, nil, nil, 0, 0, ::Zip::Entry::DEFLATED, @compression_level) + end new_entry.gather_fileinfo_from_srcpath(src_path) new_entry.dirty = true @entry_set << new_entry diff --git a/test/file_test.rb b/test/file_test.rb index 8a5dd432..1d05007b 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -8,7 +8,7 @@ class ZipFileTest < MiniTest::Test OK_DELETE_MOVED_FILE = 'test/data/generated/okToDeleteMoved.txt' def teardown - ::Zip.write_zip64_support = false + ::Zip.reset! end def test_create_from_scratch_to_buffer @@ -77,7 +77,7 @@ def test_get_output_stream assert_equal(count + 1, zf.size) assert_equal('Putting stuff in data/generated/empty.txt', zf.read('test/data/generated/empty.txt')) - custom_entry_args = [TEST_COMMENT, TEST_EXTRA, TEST_COMPRESSED_SIZE, TEST_CRC, ::Zip::Entry::STORED, TEST_SIZE, TEST_TIME] + custom_entry_args = [TEST_COMMENT, TEST_EXTRA, TEST_COMPRESSED_SIZE, TEST_CRC, ::Zip::Entry::STORED, ::Zlib::BEST_SPEED, TEST_SIZE, TEST_TIME] zf.get_output_stream('entry_with_custom_args.txt', nil, *custom_entry_args) do |os| os.write 'Some data' end @@ -188,7 +188,7 @@ def test_cleans_up_tempfiles_after_close assert_equal(false, File.exist?(@tempfile_path)) end - def test_add + def test_add_default_compression src_file = 'test/data/file2.txt' entry_name = 'newEntryName.rb' assert(::File.exist?(src_file)) @@ -197,9 +197,84 @@ def test_add zf.close zf_read = ::Zip::File.new(EMPTY_FILENAME) + entry = zf_read.entries.first assert_equal('', zf_read.comment) assert_equal(1, zf_read.entries.length) assert_equal(entry_name, zf_read.entries.first.name) + assert_equal(File.size(src_file), entry.size) + assert_equal(8_764, entry.compressed_size) + AssertEntry.assert_contents(src_file, + zf_read.get_input_stream(entry_name, &:read)) + end + + def test_add_best_compression + src_file = 'test/data/file2.txt' + entry_name = 'newEntryName.rb' + assert(::File.exist?(src_file)) + zf = ::Zip::File.new(EMPTY_FILENAME, ::Zip::File::CREATE, false, { compression_level: Zlib::BEST_COMPRESSION }) + zf.add(entry_name, src_file) + zf.close + + zf_read = ::Zip::File.new(EMPTY_FILENAME) + entry = zf_read.entries.first + assert_equal(1, zf_read.entries.length) + assert_equal(File.size(src_file), entry.size) + assert_equal(8_658, entry.compressed_size) + AssertEntry.assert_contents(src_file, + zf_read.get_input_stream(entry_name, &:read)) + end + + def test_add_best_compression_as_default + ::Zip.default_compression = Zlib::BEST_COMPRESSION + + src_file = 'test/data/file2.txt' + entry_name = 'newEntryName.rb' + assert(::File.exist?(src_file)) + zf = ::Zip::File.new(EMPTY_FILENAME, ::Zip::File::CREATE) + zf.add(entry_name, src_file) + zf.close + + zf_read = ::Zip::File.new(EMPTY_FILENAME) + entry = zf_read.entries.first + assert_equal(1, zf_read.entries.length) + assert_equal(File.size(src_file), entry.size) + assert_equal(8_658, entry.compressed_size) + AssertEntry.assert_contents(src_file, + zf_read.get_input_stream(entry_name, &:read)) + end + + def test_add_best_speed + src_file = 'test/data/file2.txt' + entry_name = 'newEntryName.rb' + assert(::File.exist?(src_file)) + zf = ::Zip::File.new(EMPTY_FILENAME, ::Zip::File::CREATE, false, { compression_level: Zlib::BEST_SPEED }) + zf.add(entry_name, src_file) + zf.close + + zf_read = ::Zip::File.new(EMPTY_FILENAME) + entry = zf_read.entries.first + assert_equal(1, zf_read.entries.length) + assert_equal(File.size(src_file), entry.size) + assert_equal(10_938, entry.compressed_size) + AssertEntry.assert_contents(src_file, + zf_read.get_input_stream(entry_name, &:read)) + end + + def test_add_best_speed_as_default + ::Zip.default_compression = Zlib::BEST_SPEED + + src_file = 'test/data/file2.txt' + entry_name = 'newEntryName.rb' + assert(::File.exist?(src_file)) + zf = ::Zip::File.new(EMPTY_FILENAME, ::Zip::File::CREATE) + zf.add(entry_name, src_file) + zf.close + + zf_read = ::Zip::File.new(EMPTY_FILENAME) + entry = zf_read.entries.first + assert_equal(1, zf_read.entries.length) + assert_equal(File.size(src_file), entry.size) + assert_equal(10_938, entry.compressed_size) AssertEntry.assert_contents(src_file, zf_read.get_input_stream(entry_name, &:read)) end From d4bc24dcb38a7b0832fe39d700ea8f88c336da22 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 24 May 2020 18:50:18 +0100 Subject: [PATCH 174/469] Clean up `OutputStream` internals. There was some fairly odd stuff going on in `put_next_entry` that allowed for data within an `Entry` to be overridden and prevented an `Entry` from being a single point of truth. Fixing this also simplifies the code within `File` and still passes all tests. Also, fixing the above means we can stop passing the compression level around as a parameter and use the value stored in each `Entry` directly. Let's keep `compression_level` out of the `Entry` public API though as it only makes sense when writing an `Entry`: there doesn't seem to be an obvious way to read what level of compression was used when reading an `Entry` from a zip file. --- lib/zip/entry.rb | 16 +++++++--------- lib/zip/output_stream.rb | 19 +++++++------------ test/file_test.rb | 1 + 3 files changed, 15 insertions(+), 21 deletions(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index e2160432..dc29a579 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -13,8 +13,7 @@ class Entry :restore_times, :restore_permissions, :restore_ownership, :unix_uid, :unix_gid, :unix_perms, :dirty - attr_reader :ftype, :filepath # :nodoc: - attr_writer :compression_level # :nodoc: + attr_reader :compression_level, :ftype, :filepath # :nodoc: def set_default_vars_values @local_header_offset = 0 @@ -579,14 +578,13 @@ def gather_fileinfo_from_srcpath(src_path) # :nodoc: def write_to_zip_output_stream(zip_output_stream) #:nodoc:all if @ftype == :directory - zip_output_stream.put_next_entry(self, nil, nil, ::Zip::Entry::STORED) + @compression_method = ::Zip::Entry::STORED + zip_output_stream.put_next_entry(self) elsif @filepath - zip_output_stream.put_next_entry( - self, nil, nil, - compression_method || ::Zip::Entry::DEFLATED, - @compression_level || ::Zip.default_compression - ) - get_input_stream { |is| ::Zip::IOExtras.copy_stream(zip_output_stream, is) } + zip_output_stream.put_next_entry(self) + get_input_stream do |is| + ::Zip::IOExtras.copy_stream(zip_output_stream, is) + end else zip_output_stream.copy_raw_entry(self) end diff --git a/lib/zip/output_stream.rb b/lib/zip/output_stream.rb index 4168e60b..d4ca32c2 100644 --- a/lib/zip/output_stream.rb +++ b/lib/zip/output_stream.rb @@ -95,15 +95,10 @@ def put_next_entry(entry_name, comment = nil, extra = nil, compression_method = new_entry = if entry_name.kind_of?(Entry) entry_name else - Entry.new(@file_name, entry_name.to_s) + Entry.new(@file_name, entry_name.to_s, comment, extra, 0, 0, compression_method, level) end - new_entry.comment = comment unless comment.nil? - unless extra.nil? - new_entry.extra = extra.kind_of?(ExtraField) ? extra : ExtraField.new(extra.to_s) - end - new_entry.compression_method = compression_method unless compression_method.nil? - new_entry.compression_level = level unless level.nil? - init_next_entry(new_entry, level) + + init_next_entry(new_entry) @current_entry = new_entry end @@ -143,19 +138,19 @@ def finalize_current_entry @compressor = ::Zip::NullCompressor.instance end - def init_next_entry(entry, level) + def init_next_entry(entry) finalize_current_entry @entry_set << entry entry.write_local_entry(@output_stream) @encrypter.reset! @output_stream << @encrypter.header(entry.mtime) - @compressor = get_compressor(entry, level) + @compressor = get_compressor(entry) end - def get_compressor(entry, level) + def get_compressor(entry) case entry.compression_method when Entry::DEFLATED - ::Zip::Deflater.new(@output_stream, level, @encrypter) + ::Zip::Deflater.new(@output_stream, entry.compression_level, @encrypter) when Entry::STORED ::Zip::PassThruCompressor.new(@output_stream) else diff --git a/test/file_test.rb b/test/file_test.rb index 1d05007b..0d72a1d3 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -87,6 +87,7 @@ def test_get_output_stream assert_equal(custom_entry_args[2], entry.compressed_size) assert_equal(custom_entry_args[3], entry.crc) assert_equal(custom_entry_args[4], entry.compression_method) + assert_equal(custom_entry_args[5], entry.compression_level) assert_equal(custom_entry_args[6], entry.size) assert_equal(custom_entry_args[7], entry.time) From 072fa27e786cb01e570f6e5b4e2ce4cfd1bcb945 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 7 Jun 2020 17:38:46 +0100 Subject: [PATCH 175/469] Refactor `Entry#compression_method` access. As per the conversation here [1], make `compression_method` a method and enforce the correct type of compression for directories by default. [1] https://github.com/rubyzip/rubyzip/pull/448#discussion_r436268506 --- .rubocop_todo.yml | 2 +- lib/zip/entry.rb | 25 ++++++++++++++++--------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 04879430..04be2680 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -13,7 +13,7 @@ Metrics/ClassLength: # Offense count: 26 Metrics/CyclomaticComplexity: - Max: 14 + Max: 15 # Offense count: 44 # Configuration parameters: CountComments, ExcludedMethods. diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index dc29a579..810567be 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -6,7 +6,7 @@ class Entry # Language encoding flag (EFS) bit EFS = 0b100000000000 - attr_accessor :comment, :compressed_size, :crc, :extra, :compression_method, + attr_accessor :comment, :compressed_size, :crc, :extra, :name, :size, :local_header_offset, :zipfile, :fstype, :external_file_attributes, :internal_file_attributes, :gp_flags, :header_signature, :follow_symlinks, @@ -53,24 +53,24 @@ def check_name(name) end def initialize(*args) - name = args[1] || '' - check_name(name) + @name = args[1] || '' + check_name(@name) set_default_vars_values @fstype = ::Zip::RUNNING_ON_WINDOWS ? ::Zip::FSTYPE_FAT : ::Zip::FSTYPE_UNIX + @ftype = name_is_directory? ? :directory : :file @zipfile = args[0] || '' - @name = name @comment = args[2] || '' @extra = args[3] || '' @compressed_size = args[4] || 0 @crc = args[5] || 0 - @compression_method = args[6] || ::Zip::Entry::DEFLATED + @compression_method = + (@ftype == :directory ? STORED : args[6] || DEFLATED) @compression_level = args[7] || ::Zip.default_compression @size = args[8] || 0 @time = args[9] || ::Zip::DOSTime.now - @ftype = name_is_directory? ? :directory : :file @extra = ::Zip::ExtraField.new(@extra.to_s) unless @extra.kind_of?(::Zip::ExtraField) end @@ -104,6 +104,14 @@ def time=(value) @time = value end + def compression_method + @ftype == :directory ? STORED : @compression_method + end + + def compression_method=(method) + @compression_method = (@ftype == :directory ? STORED : method) + end + def file_type_is?(type) raise InternalError, "current filetype is unknown: #{inspect}" unless @ftype @@ -287,7 +295,7 @@ def pack_local_entry [::Zip::LOCAL_ENTRY_SIGNATURE, @version_needed_to_extract, # version needed to extract @gp_flags, # @gp_flags - @compression_method, + compression_method, @time.to_binary_dos_time, # @last_mod_time @time.to_binary_dos_date, # @last_mod_date @crc, @@ -453,7 +461,7 @@ def pack_c_dir_entry @fstype, # filesystem type @version_needed_to_extract, # @versionNeededToExtract @gp_flags, # @gp_flags - @compression_method, + compression_method, @time.to_binary_dos_time, # @last_mod_time @time.to_binary_dos_date, # @last_mod_date @crc, @@ -578,7 +586,6 @@ def gather_fileinfo_from_srcpath(src_path) # :nodoc: def write_to_zip_output_stream(zip_output_stream) #:nodoc:all if @ftype == :directory - @compression_method = ::Zip::Entry::STORED zip_output_stream.put_next_entry(self) elsif @filepath zip_output_stream.put_next_entry(self) From 2775f529b4c3e3b9d37733cf67798e3431cab78d Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 13 Jun 2020 17:33:39 +0100 Subject: [PATCH 176/469] Set the compression level general purpose flags. --- .rubocop_todo.yml | 2 +- lib/zip/entry.rb | 22 ++++++++++++++++++++++ test/entry_test.rb | 26 ++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 04be2680..0a045c53 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -9,7 +9,7 @@ # Offense count: 5 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 580 + Max: 610 # Offense count: 26 Metrics/CyclomaticComplexity: diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 810567be..5aec84bd 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -72,6 +72,7 @@ def initialize(*args) @time = args[9] || ::Zip::DOSTime.now @extra = ::Zip::ExtraField.new(@extra.to_s) unless @extra.kind_of?(::Zip::ExtraField) + set_compression_level_flags end def encrypted? @@ -689,6 +690,27 @@ def data_descriptor_size (@gp_flags & 0x0008) > 0 ? 16 : 0 end + # For DEFLATED compression *only*: set the general purpose flags 1 and 2 to + # indicate compression level. This seems to be mainly cosmetic but they are + # generally set by other tools - including in docx files. It is these flags + # that are used by commandline tools (and elsewhere) to give an indication + # of how compressed a file is. See the PKWARE APPNOTE for more information: + # https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT + # + # It's safe to simply OR these flags here as compression_level is read only. + def set_compression_level_flags + return unless compression_method == DEFLATED + + case @compression_level + when 1 + @gp_flags |= 0b110 + when 2 + @gp_flags |= 0b100 + when 8, 9 + @gp_flags |= 0b010 + end + end + # create a zip64 extra information field if we need one def prep_zip64_extra(for_local_header) #:nodoc:all return unless ::Zip.write_zip64_support diff --git a/test/entry_test.rb b/test/entry_test.rb index 541ba41f..aabb2fdb 100644 --- a/test/entry_test.rb +++ b/test/entry_test.rb @@ -170,4 +170,30 @@ def test_incomplete? entry.gp_flags = 0 assert_equal(false, entry.incomplete?) end + + def test_compression_level_flags + [ + [Zip.default_compression, 0], + [0, 0], + [1, 6], + [2, 4], + [3, 0], + [7, 0], + [8, 2], + [9, 2] + ].each do |level, flags| + # Check flags are set correctly when DEFLATED is specified. + e_def = Zip::Entry.new('', '', '', '', 0, 0, Zip::Entry::DEFLATED, level) + assert_equal(flags, e_def.gp_flags & 0b110) + + # Check that flags are not set when STORED is specified. + e_sto = Zip::Entry.new('', '', '', '', 0, 0, Zip::Entry::STORED, level) + assert_equal(0, e_sto.gp_flags & 0b110) + end + + # Check that a directory entry's flags are not set, even if DEFLATED + # is specified. + e_dir = Zip::Entry.new('', 'd/', '', '', 0, 0, Zip::Entry::DEFLATED, 1) + assert_equal(0, e_dir.gp_flags & 0b110) + end end From e4ceedaa5805e2716ba3cfe304d36a7ed422f227 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 2 Aug 2020 17:51:08 +0100 Subject: [PATCH 177/469] Use keyword arguments for the `Entry` initializer. This greatly simplifies the creation of `Entry` objects when only a couple of fields are not set to their defaults, while at the same time allowing an `Entry` to be fully configured at creation time if appropriate. This fundamentally changes the `Entry` API and means that some convenience methods in `OutputStream` and `File` have needed to be refactored. --- lib/zip/entry.rb | 34 ++++++---- lib/zip/file.rb | 16 ++++- lib/zip/output_stream.rb | 11 ++- test/central_directory_test.rb | 85 ++++++++++++++++------- test/entry_set_test.rb | 28 +++++--- test/entry_test.rb | 119 ++++++++++++++++++++------------- test/file_test.rb | 5 +- test/local_entry_test.rb | 26 ++++--- test/output_stream_test.rb | 4 +- 9 files changed, 215 insertions(+), 113 deletions(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 5aec84bd..0e2dfd8c 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -52,26 +52,32 @@ def check_name(name) raise ::Zip::EntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /" end - def initialize(*args) - @name = args[1] || '' + 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 + ) + @name = name check_name(@name) set_default_vars_values @fstype = ::Zip::RUNNING_ON_WINDOWS ? ::Zip::FSTYPE_FAT : ::Zip::FSTYPE_UNIX @ftype = name_is_directory? ? :directory : :file - @zipfile = args[0] || '' - @comment = args[2] || '' - @extra = args[3] || '' - @compressed_size = args[4] || 0 - @crc = args[5] || 0 - @compression_method = - (@ftype == :directory ? STORED : args[6] || DEFLATED) - @compression_level = args[7] || ::Zip.default_compression - @size = args[8] || 0 - @time = args[9] || ::Zip::DOSTime.now - - @extra = ::Zip::ExtraField.new(@extra.to_s) unless @extra.kind_of?(::Zip::ExtraField) + @zipfile = zipfile + @comment = comment + @compression_method = compression_method + @compression_level = compression_level + + @compressed_size = compressed_size + @crc = crc + @size = size + @time = time + @extra = + extra.kind_of?(ExtraField) ? extra : ExtraField.new(extra.to_s) + set_compression_level_flags end diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 600e6160..c96d6615 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -276,7 +276,12 @@ def get_output_stream(entry, permission_int = nil, comment = nil, if entry.kind_of?(Entry) entry else - Entry.new(@name, entry.to_s, comment, extra, compressed_size, crc, compression_method, compression_level, size, time) + Entry.new( + @name, entry.to_s, comment: comment, extra: extra, + compressed_size: compressed_size, crc: crc, size: size, + compression_method: compression_method, + compression_level: compression_level, time: time + ) end if new_entry.directory? raise ArgumentError, @@ -305,7 +310,10 @@ def add(entry, src_path, &continue_on_exists_proc) new_entry = if entry.kind_of?(::Zip::Entry) entry else - ::Zip::Entry.new(@name, entry.to_s, nil, nil, 0, 0, ::Zip::Entry::DEFLATED, @compression_level) + ::Zip::Entry.new( + @name, entry.to_s, + compression_level: @compression_level + ) end new_entry.gather_fileinfo_from_srcpath(src_path) new_entry.dirty = true @@ -315,7 +323,9 @@ def add(entry, src_path, &continue_on_exists_proc) # Convenience method for adding the contents of a file to the archive # in Stored format (uncompressed) def add_stored(entry, src_path, &continue_on_exists_proc) - entry = ::Zip::Entry.new(@name, entry.to_s, nil, nil, nil, nil, ::Zip::Entry::STORED) + entry = ::Zip::Entry.new( + @name, entry.to_s, compression_method: ::Zip::Entry::STORED + ) add(entry, src_path, &continue_on_exists_proc) end diff --git a/lib/zip/output_stream.rb b/lib/zip/output_stream.rb index d4ca32c2..65b7d620 100644 --- a/lib/zip/output_stream.rb +++ b/lib/zip/output_stream.rb @@ -89,13 +89,20 @@ def close_buffer # Closes the current entry and opens a new for writing. # +entry+ can be a ZipEntry object or a string. - def put_next_entry(entry_name, comment = nil, extra = nil, compression_method = Entry::DEFLATED, level = Zip.default_compression) + def put_next_entry( + entry_name, comment = '', extra = ExtraField.new, + compression_method = Entry::DEFLATED, level = Zip.default_compression + ) raise Error, 'zip stream is closed' if @closed new_entry = if entry_name.kind_of?(Entry) entry_name else - Entry.new(@file_name, entry_name.to_s, comment, extra, 0, 0, compression_method, level) + Entry.new( + @file_name, entry_name.to_s, comment: comment, + extra: extra, compression_method: compression_method, + compression_level: level + ) end init_next_entry(new_entry) diff --git a/test/central_directory_test.rb b/test/central_directory_test.rb index c4f7afa0..b2fce5f1 100644 --- a/test/central_directory_test.rb +++ b/test/central_directory_test.rb @@ -38,9 +38,14 @@ def test_read_from_truncated_zip_file end def test_write_to_stream - entries = [::Zip::Entry.new('file.zip', 'flimse', 'myComment', 'somethingExtra'), - ::Zip::Entry.new('file.zip', 'secondEntryName'), - ::Zip::Entry.new('file.zip', 'lastEntry.txt', 'Has a comment too')] + entries = [ + ::Zip::Entry.new( + 'file.zip', 'flimse', + comment: 'myComment', extra: 'somethingExtra' + ), + ::Zip::Entry.new('file.zip', 'secondEntryName'), + ::Zip::Entry.new('file.zip', 'lastEntry.txt', comment: 'Has a comment') + ] cdir = ::Zip::CentralDirectory.new(entries, 'my zip comment') File.open('test/data/generated/cdirtest.bin', 'wb') do |f| @@ -57,10 +62,26 @@ def test_write_to_stream def test_write64_to_stream ::Zip.write_zip64_support = true - entries = [::Zip::Entry.new('file.zip', 'file1-little', 'comment1', '', 200, 101, ::Zip::Entry::STORED, 200), - ::Zip::Entry.new('file.zip', 'file2-big', 'comment2', '', 18_000_000_000, 102, ::Zip::Entry::DEFLATED, 20_000_000_000), - ::Zip::Entry.new('file.zip', 'file3-alsobig', 'comment3', '', 15_000_000_000, 103, ::Zip::Entry::DEFLATED, 21_000_000_000), - ::Zip::Entry.new('file.zip', 'file4-little', 'comment4', '', 100, 104, ::Zip::Entry::DEFLATED, 121)] + entries = [ + ::Zip::Entry.new( + 'file.zip', 'file1-little', comment: 'comment1', size: 200, + compressed_size: 200, crc: 101, + compression_method: ::Zip::Entry::STORED + ), + ::Zip::Entry.new( + 'file.zip', 'file2-big', comment: 'comment2', + size: 20_000_000_000, compressed_size: 18_000_000_000, crc: 102 + ), + ::Zip::Entry.new( + 'file.zip', 'file3-alsobig', comment: 'comment3', + size: 21_000_000_000, compressed_size: 15_000_000_000, crc: 103 + ), + ::Zip::Entry.new( + 'file.zip', 'file4-little', comment: 'comment4', + size: 121, compressed_size: 100, crc: 104 + ) + ] + [0, 250, 18_000_000_300, 33_000_000_350].each_with_index do |offset, index| entries[index].local_header_offset = offset end @@ -80,25 +101,37 @@ def test_write64_to_stream end def test_equality - cdir1 = ::Zip::CentralDirectory.new([::Zip::Entry.new('file.zip', 'flimse', nil, - 'somethingExtra'), - ::Zip::Entry.new('file.zip', 'secondEntryName'), - ::Zip::Entry.new('file.zip', 'lastEntry.txt')], - 'my zip comment') - cdir2 = ::Zip::CentralDirectory.new([::Zip::Entry.new('file.zip', 'flimse', nil, - 'somethingExtra'), - ::Zip::Entry.new('file.zip', 'secondEntryName'), - ::Zip::Entry.new('file.zip', 'lastEntry.txt')], - 'my zip comment') - cdir3 = ::Zip::CentralDirectory.new([::Zip::Entry.new('file.zip', 'flimse', nil, - 'somethingExtra'), - ::Zip::Entry.new('file.zip', 'secondEntryName'), - ::Zip::Entry.new('file.zip', 'lastEntry.txt')], - 'comment?') - cdir4 = ::Zip::CentralDirectory.new([::Zip::Entry.new('file.zip', 'flimse', nil, - 'somethingExtra'), - ::Zip::Entry.new('file.zip', 'lastEntry.txt')], - 'comment?') + cdir1 = ::Zip::CentralDirectory.new( + [ + ::Zip::Entry.new('file.zip', 'flimse', extra: 'somethingExtra'), + ::Zip::Entry.new('file.zip', 'secondEntryName'), + ::Zip::Entry.new('file.zip', 'lastEntry.txt') + ], + 'my zip comment' + ) + cdir2 = ::Zip::CentralDirectory.new( + [ + ::Zip::Entry.new('file.zip', 'flimse', extra: 'somethingExtra'), + ::Zip::Entry.new('file.zip', 'secondEntryName'), + ::Zip::Entry.new('file.zip', 'lastEntry.txt') + ], + 'my zip comment' + ) + cdir3 = ::Zip::CentralDirectory.new( + [ + ::Zip::Entry.new('file.zip', 'flimse', extra: 'somethingExtra'), + ::Zip::Entry.new('file.zip', 'secondEntryName'), + ::Zip::Entry.new('file.zip', 'lastEntry.txt') + ], + 'comment?' + ) + cdir4 = ::Zip::CentralDirectory.new( + [ + ::Zip::Entry.new('file.zip', 'flimse', extra: 'somethingExtra'), + ::Zip::Entry.new('file.zip', 'lastEntry.txt') + ], + 'comment?' + ) assert_equal(cdir1, cdir1) assert_equal(cdir1, cdir2) diff --git a/test/entry_set_test.rb b/test/entry_set_test.rb index 4f137902..fd038deb 100644 --- a/test/entry_set_test.rb +++ b/test/entry_set_test.rb @@ -2,12 +2,12 @@ class ZipEntrySetTest < MiniTest::Test ZIP_ENTRIES = [ - ::Zip::Entry.new('zipfile.zip', 'name1', 'comment1'), - ::Zip::Entry.new('zipfile.zip', 'name3', 'comment1'), - ::Zip::Entry.new('zipfile.zip', 'name2', 'comment1'), - ::Zip::Entry.new('zipfile.zip', 'name4', 'comment1'), - ::Zip::Entry.new('zipfile.zip', 'name5', 'comment1'), - ::Zip::Entry.new('zipfile.zip', 'name6', 'comment1') + ::Zip::Entry.new('zipfile.zip', 'name1', comment: 'comment1'), + ::Zip::Entry.new('zipfile.zip', 'name3', comment: 'comment1'), + ::Zip::Entry.new('zipfile.zip', 'name2', comment: 'comment1'), + ::Zip::Entry.new('zipfile.zip', 'name4', comment: 'comment1'), + ::Zip::Entry.new('zipfile.zip', 'name5', comment: 'comment1'), + ::Zip::Entry.new('zipfile.zip', 'name6', comment: 'comment1') ] def setup @@ -20,13 +20,17 @@ def teardown def test_include assert(@zip_entry_set.include?(ZIP_ENTRIES.first)) - assert(!@zip_entry_set.include?(::Zip::Entry.new('different.zip', 'different', 'aComment'))) + assert( + !@zip_entry_set.include?( + ::Zip::Entry.new('different.zip', 'different', comment: 'aComment') + ) + ) end def test_size assert_equal(ZIP_ENTRIES.size, @zip_entry_set.size) assert_equal(ZIP_ENTRIES.size, @zip_entry_set.length) - @zip_entry_set << ::Zip::Entry.new('a', 'b', 'c') + @zip_entry_set << ::Zip::Entry.new('a', 'b', comment: 'c') assert_equal(ZIP_ENTRIES.size + 1, @zip_entry_set.length) end @@ -66,7 +70,9 @@ def test_entries end def test_find_entry - entries = [::Zip::Entry.new('zipfile.zip', 'MiXeDcAsEnAmE', 'comment1')] + entries = [ + ::Zip::Entry.new('zipfile.zip', 'MiXeDcAsEnAmE', comment: 'comment1') + ] ::Zip.case_insensitive_match = true zip_entry_set = ::Zip::EntrySet.new(entries) @@ -96,7 +102,9 @@ def test_entries_sorted_in_each end def test_compound - new_entry = ::Zip::Entry.new('zf.zip', 'new entry', "new entry's comment") + new_entry = ::Zip::Entry.new( + 'zf.zip', 'new entry', comment: "new entry's comment" + ) assert_equal(ZIP_ENTRIES.size, @zip_entry_set.size) @zip_entry_set << new_entry assert_equal(ZIP_ENTRIES.size + 1, @zip_entry_set.size) diff --git a/test/entry_test.rb b/test/entry_test.rb index aabb2fdb..6df0964c 100644 --- a/test/entry_test.rb +++ b/test/entry_test.rb @@ -4,16 +4,14 @@ class ZipEntryTest < MiniTest::Test include ZipEntryData def test_constructor_and_getters - entry = ::Zip::Entry.new(TEST_ZIPFILE, - TEST_NAME, - TEST_COMMENT, - TEST_EXTRA, - TEST_COMPRESSED_SIZE, - TEST_CRC, - TEST_COMPRESSIONMETHOD, - TEST_COMPRESSIONLEVEL, - TEST_SIZE, - TEST_TIME) + entry = ::Zip::Entry.new( + TEST_ZIPFILE, TEST_NAME, + comment: TEST_COMMENT, extra: TEST_EXTRA, + compressed_size: TEST_COMPRESSED_SIZE, + crc: TEST_CRC, size: TEST_SIZE, time: TEST_TIME, + compression_method: TEST_COMPRESSIONMETHOD, + compression_level: TEST_COMPRESSIONLEVEL + ) assert_equal(TEST_COMMENT, entry.comment) assert_equal(TEST_COMPRESSED_SIZE, entry.compressed_size) @@ -40,30 +38,54 @@ def test_is_directory_and_is_file end def test_equality - entry1 = ::Zip::Entry.new('file.zip', 'name', 'isNotCompared', - 'something extra', 123, 1234, - ::Zip::Entry::DEFLATED, ::Zip.default_compression, 10_000) - entry2 = ::Zip::Entry.new('file.zip', 'name', 'isNotComparedXXX', - 'something extra', 123, 1234, - ::Zip::Entry::DEFLATED, ::Zip.default_compression, 10_000) - entry3 = ::Zip::Entry.new('file.zip', 'name2', 'isNotComparedXXX', - 'something extra', 123, 1234, - ::Zip::Entry::DEFLATED, ::Zip.default_compression, 10_000) - entry4 = ::Zip::Entry.new('file.zip', 'name2', 'isNotComparedXXX', - 'something extraXX', 123, 1234, - ::Zip::Entry::DEFLATED, ::Zip.default_compression, 10_000) - entry5 = ::Zip::Entry.new('file.zip', 'name2', 'isNotComparedXXX', - 'something extraXX', 12, 1234, - ::Zip::Entry::DEFLATED, ::Zip.default_compression, 10_000) - entry6 = ::Zip::Entry.new('file.zip', 'name2', 'isNotComparedXXX', - 'something extraXX', 12, 123, - ::Zip::Entry::DEFLATED, ::Zip.default_compression, 10_000) - entry7 = ::Zip::Entry.new('file.zip', 'name2', 'isNotComparedXXX', - 'something extraXX', 12, 123, - ::Zip::Entry::STORED, ::Zip.default_compression, 10_000) - entry8 = ::Zip::Entry.new('file.zip', 'name2', 'isNotComparedXXX', - 'something extraXX', 12, 123, - ::Zip::Entry::STORED, ::Zip.default_compression, 100_000) + entry1 = ::Zip::Entry.new( + 'file.zip', 'name', + comment: 'isNotCompared', extra: 'something extra', + compressed_size: 123, crc: 1234, size: 10_000 + ) + + entry2 = ::Zip::Entry.new( + 'file.zip', 'name', + comment: 'isNotComparedXXX', extra: 'something extra', + compressed_size: 123, crc: 1234, size: 10_000 + ) + + entry3 = ::Zip::Entry.new( + 'file.zip', 'name2', + comment: 'isNotComparedXXX', extra: 'something extra', + compressed_size: 123, crc: 1234, size: 10_000 + ) + + entry4 = ::Zip::Entry.new( + 'file.zip', 'name2', + comment: 'isNotComparedXXX', extra: 'something extraXX', + compressed_size: 123, crc: 1234, size: 10_000 + ) + + entry5 = ::Zip::Entry.new( + 'file.zip', 'name2', + comment: 'isNotComparedXXX', extra: 'something extraXX', + compressed_size: 12, crc: 1234, size: 10_000 + ) + + entry6 = ::Zip::Entry.new( + 'file.zip', 'name2', + comment: 'isNotComparedXXX', extra: 'something extraXX', + compressed_size: 12, crc: 123, size: 10_000 + ) + + entry7 = ::Zip::Entry.new( + 'file.zip', 'name2', comment: 'isNotComparedXXX', + extra: 'something extraXX', compressed_size: 12, crc: 123, size: 10_000, + compression_method: ::Zip::Entry::STORED + ) + + entry8 = ::Zip::Entry.new( + 'file.zip', 'name2', + comment: 'isNotComparedXXX', extra: 'something extraXX', + compressed_size: 12, crc: 123, size: 100_000, + compression_method: ::Zip::Entry::STORED + ) assert_equal(entry1, entry1) assert_equal(entry1, entry2) @@ -131,13 +153,11 @@ def test_store_file_without_compression end zipfile = Zip::File.open('/tmp/no_compress.zip', Zip::File::CREATE) - mimetype_entry = Zip::Entry.new(zipfile, # @zipfile - 'mimetype', # @name - '', # @comment - '', # @extra - 0, # @compressed_size - 0, # @crc - Zip::Entry::STORED) # @comppressed_method + mimetype_entry = Zip::Entry.new( + zipfile, # @zipfile + 'mimetype', # @name + compression_method: Zip::Entry::STORED + ) zipfile.add(mimetype_entry, 'test/data/mimetype') @@ -182,18 +202,27 @@ def test_compression_level_flags [8, 2], [9, 2] ].each do |level, flags| - # Check flags are set correctly when DEFLATED is specified. - e_def = Zip::Entry.new('', '', '', '', 0, 0, Zip::Entry::DEFLATED, level) + # Check flags are set correctly when DEFLATED is (implicitly) specified. + e_def = Zip::Entry.new( + '', '', + compression_level: level + ) assert_equal(flags, e_def.gp_flags & 0b110) # Check that flags are not set when STORED is specified. - e_sto = Zip::Entry.new('', '', '', '', 0, 0, Zip::Entry::STORED, level) + e_sto = Zip::Entry.new( + '', '', + compression_method: Zip::Entry::STORED, + compression_level: level + ) assert_equal(0, e_sto.gp_flags & 0b110) end # Check that a directory entry's flags are not set, even if DEFLATED # is specified. - e_dir = Zip::Entry.new('', 'd/', '', '', 0, 0, Zip::Entry::DEFLATED, 1) + e_dir = Zip::Entry.new( + '', 'd/', compression_method: Zip::Entry::DEFLATED, compression_level: 1 + ) assert_equal(0, e_dir.gp_flags & 0b110) end end diff --git a/test/file_test.rb b/test/file_test.rb index 0d72a1d3..6c52adfc 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -77,7 +77,10 @@ def test_get_output_stream assert_equal(count + 1, zf.size) assert_equal('Putting stuff in data/generated/empty.txt', zf.read('test/data/generated/empty.txt')) - custom_entry_args = [TEST_COMMENT, TEST_EXTRA, TEST_COMPRESSED_SIZE, TEST_CRC, ::Zip::Entry::STORED, ::Zlib::BEST_SPEED, TEST_SIZE, TEST_TIME] + custom_entry_args = [ + TEST_COMMENT, TEST_EXTRA, TEST_COMPRESSED_SIZE, TEST_CRC, + ::Zip::Entry::STORED, ::Zlib::BEST_SPEED, TEST_SIZE, TEST_TIME + ] zf.get_output_stream('entry_with_custom_args.txt', nil, *custom_entry_args) do |os| os.write 'Some data' end diff --git a/test/local_entry_test.rb b/test/local_entry_test.rb index 28148039..3a1c91ff 100644 --- a/test/local_entry_test.rb +++ b/test/local_entry_test.rb @@ -53,9 +53,11 @@ def test_read_local_entry_from_truncated_zip_file end def test_write_entry - entry = ::Zip::Entry.new('file.zip', 'entry_name', 'my little comment', - 'thisIsSomeExtraInformation', 100, 987_654, - ::Zip::Entry::DEFLATED, 400) + entry = ::Zip::Entry.new( + 'file.zip', 'entry_name', comment: 'my little comment', size: 400, + extra: 'thisIsSomeExtraInformation', compressed_size: 100, crc: 987_654 + ) + write_to_file(LEH_FILE, CEH_FILE, entry) local_entry, central_entry = read_from_file(LEH_FILE, CEH_FILE) assert( @@ -68,9 +70,10 @@ def test_write_entry def test_write_entry_with_zip64 ::Zip.write_zip64_support = true - entry = ::Zip::Entry.new('file.zip', 'entry_name', 'my little comment', - 'thisIsSomeExtraInformation', 100, 987_654, - ::Zip::Entry::DEFLATED, 400) + entry = ::Zip::Entry.new( + 'file.zip', 'entry_name', comment: 'my little comment', size: 400, + extra: 'thisIsSomeExtraInformation', compressed_size: 100, crc: 987_654 + ) write_to_file(LEH_FILE, CEH_FILE, entry) local_entry, central_entry = read_from_file(LEH_FILE, CEH_FILE) @@ -92,11 +95,12 @@ def test_write_entry_with_zip64 def test_write_64entry ::Zip.write_zip64_support = true - entry = ::Zip::Entry.new('bigfile.zip', 'entry_name', 'my little equine', - 'malformed extra field because why not', - 0x7766554433221100, 0xDEADBEEF, - ::Zip::Entry::DEFLATED, ::Zip.default_compression, - 0x9988776655443322) + entry = ::Zip::Entry.new( + 'bigfile.zip', 'entry_name', comment: 'my little equine', + extra: 'malformed extra field because why not', size: 0x9988776655443322, + compressed_size: 0x7766554433221100, crc: 0xDEADBEEF + ) + write_to_file(LEH_FILE, CEH_FILE, entry) local_entry, central_entry = read_from_file(LEH_FILE, CEH_FILE) compare_local_entry_headers(entry, local_entry) diff --git a/test/output_stream_test.rb b/test/output_stream_test.rb index 79fb8f71..c89cbbcc 100644 --- a/test/output_stream_test.rb +++ b/test/output_stream_test.rb @@ -92,7 +92,9 @@ def test_put_next_entry def test_put_next_entry_using_zip_entry_creates_entries_with_correct_timestamps file = ::File.open('test/data/file2.txt', 'rb') ::Zip::OutputStream.open(TEST_ZIP.zip_name) do |zos| - zip_entry = ::Zip::Entry.new(zos, file.path, '', '', 0, 0, ::Zip::Entry::DEFLATED, ::Zip.default_compression, 0, ::Zip::DOSTime.at(file.mtime)) + zip_entry = ::Zip::Entry.new( + zos, file.path, time: ::Zip::DOSTime.at(file.mtime) + ) zos.put_next_entry(zip_entry) zos << file.read end From 156b0f3dee1dbb9f7c4dbc0012251990aec1da02 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 15 Jun 2020 00:16:17 +0100 Subject: [PATCH 178/469] Tidy up accessors in `Entry`. Remove a load of accessors from the 'public' API by not documenting them, and remove access to `header_signature` completely. Also, `Entry#extra` has been created to allow extra fields to be set correctly after initialization. --- lib/zip/entry.rb | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 0e2dfd8c..32c0b165 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -6,14 +6,14 @@ class Entry # Language encoding flag (EFS) bit EFS = 0b100000000000 - attr_accessor :comment, :compressed_size, :crc, :extra, - :name, :size, :local_header_offset, :zipfile, :fstype, :external_file_attributes, - :internal_file_attributes, - :gp_flags, :header_signature, :follow_symlinks, - :restore_times, :restore_permissions, :restore_ownership, - :unix_uid, :unix_gid, :unix_perms, - :dirty - attr_reader :compression_level, :ftype, :filepath # :nodoc: + attr_accessor :comment, :compressed_size, :follow_symlinks, :name, + :restore_ownership, :restore_permissions, :restore_times, + :size, :unix_gid, :unix_perms, :unix_uid, :zipfile + + attr_accessor :crc, :dirty, :external_file_attributes, :fstype, :gp_flags, + :internal_file_attributes, :local_header_offset # :nodoc: + + attr_reader :extra, :compression_level, :ftype, :filepath # :nodoc: def set_default_vars_values @local_header_offset = 0 @@ -89,6 +89,14 @@ def incomplete? gp_flags & 8 == 8 end + def extra=(field) + @extra = if field.nil? + ExtraField.new + else + field.kind_of?(ExtraField) ? field : ExtraField.new(field.to_s) + end + end + def time if @extra['UniversalTime'] @extra['UniversalTime'].mtime @@ -384,7 +392,7 @@ def check_c_dir_entry_static_header_length(buf) end def check_c_dir_entry_signature - return if header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE + return if @header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE raise Error, "Zip local header magic not found at location '#{local_header_offset}'" end From 0620fba13dd75ddb0d4d4d670db6bdae7a04fd98 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 20 Jun 2020 16:03:46 +0100 Subject: [PATCH 179/469] Don't use raw numbers for Entry compression types. Constants for Store and Deflate are already available, so use them. It might be sensible to remove these local versions, but they do have their uses as a shortened form. --- lib/zip/entry.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 32c0b165..48b88cd5 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -1,8 +1,9 @@ require 'pathname' module Zip class Entry - STORED = 0 - DEFLATED = 8 + STORED = ::Zip::COMPRESSION_METHOD_STORE + DEFLATED = ::Zip::COMPRESSION_METHOD_DEFLATE + # Language encoding flag (EFS) bit EFS = 0b100000000000 From 5201cd2ea35bdd1087e39b39a5b53ee80ab5adfd Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 20 Jun 2020 17:34:57 +0100 Subject: [PATCH 180/469] Make sure tests that change Zip defaults reset properly. --- test/case_sensitivity_test.rb | 2 +- test/deflater_test.rb | 4 ++++ test/encryption_test.rb | 3 +-- test/entry_test.rb | 4 ++++ test/local_entry_test.rb | 2 +- test/unicode_file_names_and_comments_test.rb | 4 ++++ 6 files changed, 15 insertions(+), 4 deletions(-) diff --git a/test/case_sensitivity_test.rb b/test/case_sensitivity_test.rb index 1c89551a..fdbee8e3 100644 --- a/test/case_sensitivity_test.rb +++ b/test/case_sensitivity_test.rb @@ -7,7 +7,7 @@ class ZipCaseSensitivityTest < MiniTest::Test ['test/data/file2.txt', 'testFILE.rb']] def teardown - ::Zip.case_insensitive_match = false + ::Zip.reset! end # Ensure that everything functions normally when +case_insensitive_match = false+ diff --git a/test/deflater_test.rb b/test/deflater_test.rb index 2506f920..35d7b0c6 100644 --- a/test/deflater_test.rb +++ b/test/deflater_test.rb @@ -8,6 +8,10 @@ class DeflaterTest < MiniTest::Test DEFAULT_COMP_FILE = 'test/data/generated/compressiontest_default_compression.bin' NO_COMP_FILE = 'test/data/generated/compressiontest_no_compression.bin' + def teardown + Zip.reset! + end + def test_output_operator txt = load_file('test/data/file2.txt') deflate(txt, DEFLATER_TEST_FILE) diff --git a/test/encryption_test.rb b/test/encryption_test.rb index d3ed5ffb..7cb39ca4 100644 --- a/test/encryption_test.rb +++ b/test/encryption_test.rb @@ -5,12 +5,11 @@ class EncryptionTest < MiniTest::Test INPUT_FILE1 = 'test/data/file1.txt' def setup - @default_compression = Zip.default_compression Zip.default_compression = ::Zlib::DEFAULT_COMPRESSION end def teardown - Zip.default_compression = @default_compression + Zip.reset! end def test_encrypt diff --git a/test/entry_test.rb b/test/entry_test.rb index 6df0964c..bf9efa3a 100644 --- a/test/entry_test.rb +++ b/test/entry_test.rb @@ -3,6 +3,10 @@ class ZipEntryTest < MiniTest::Test include ZipEntryData + def teardown + ::Zip.reset! + end + def test_constructor_and_getters entry = ::Zip::Entry.new( TEST_ZIPFILE, TEST_NAME, diff --git a/test/local_entry_test.rb b/test/local_entry_test.rb index 3a1c91ff..1317fb45 100644 --- a/test/local_entry_test.rb +++ b/test/local_entry_test.rb @@ -5,7 +5,7 @@ class ZipLocalEntryTest < MiniTest::Test LEH_FILE = 'test/data/generated/localEntryHeader.bin' def teardown - ::Zip.write_zip64_support = false + ::Zip.reset! end def test_read_local_entry_header_of_first_test_zip_entry diff --git a/test/unicode_file_names_and_comments_test.rb b/test/unicode_file_names_and_comments_test.rb index 4d2fc20f..7950e289 100644 --- a/test/unicode_file_names_and_comments_test.rb +++ b/test/unicode_file_names_and_comments_test.rb @@ -3,6 +3,10 @@ class ZipUnicodeFileNamesAndComments < MiniTest::Test FILENAME = File.join(File.dirname(__FILE__), 'test1.zip') + def teardown + ::Zip.reset! + end + def test_unicode_file_name file_entrys = ['текстовыйфайл.txt', 'Résumé.txt', '슬레이어스휘.txt'] directory_entrys = ['папка/текстовыйфайл.txt', 'Résumé/Résumé.txt', '슬레이어스휘/슬레이어스휘.txt'] From cf3f4339f6dc048ea61c854cc2e4588c7a0f39bc Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 20 Jun 2020 17:36:17 +0100 Subject: [PATCH 181/469] Make sure that compression method is STORE for level 0. Whatever the compression method that is set by the user, if the compression level is set to 0 (no compression), then the entry should be STORED. This mimics commandline tool behaviour and matches user expectations. --- lib/zip/entry.rb | 4 +++- test/entry_test.rb | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 48b88cd5..d036bb7d 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -121,7 +121,9 @@ def time=(value) end def compression_method - @ftype == :directory ? STORED : @compression_method + return STORED if @ftype == :directory || @compression_level == 0 + + @compression_method end def compression_method=(method) diff --git a/test/entry_test.rb b/test/entry_test.rb index bf9efa3a..dda0a7ba 100644 --- a/test/entry_test.rb +++ b/test/entry_test.rb @@ -229,4 +229,29 @@ def test_compression_level_flags ) assert_equal(0, e_dir.gp_flags & 0b110) end + + def test_compression_method_reader + [ + [Zip.default_compression, Zip::Entry::DEFLATED], + [0, Zip::Entry::STORED], + [1, Zip::Entry::DEFLATED], + [9, Zip::Entry::DEFLATED] + ].each do |level, method| + # Check that the correct method is returned when DEFLATED is specified. + entry = Zip::Entry.new(compression_level: level) + assert_equal(method, entry.compression_method) + end + + # Check that the correct method is returned when STORED is specified. + entry = Zip::Entry.new( + compression_method: Zip::Entry::STORED, compression_level: 1 + ) + assert_equal(Zip::Entry::STORED, entry.compression_method) + + # Check that directories are always STORED, whatever level is specified. + entry = Zip::Entry.new( + '', 'd/', compression_method: Zip::Entry::DEFLATED, compression_level: 1 + ) + assert_equal(Zip::Entry::STORED, entry.compression_method) + end end From f1dd724a3ab30190cc9422855e5eb5cf6f02645b Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Fri, 7 Aug 2020 19:45:56 +0100 Subject: [PATCH 182/469] Use constants for the compression level gp flags. --- lib/zip/entry.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index d036bb7d..c56dd052 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -7,6 +7,11 @@ class Entry # Language encoding flag (EFS) bit EFS = 0b100000000000 + # Compression level flags (used as part of the gp flags). + COMPRESSION_LEVEL_SUPERFAST_GPFLAG = 0b110 + COMPRESSION_LEVEL_FAST_GPFLAG = 0b100 + COMPRESSION_LEVEL_MAX_GPFLAG = 0b010 + attr_accessor :comment, :compressed_size, :follow_symlinks, :name, :restore_ownership, :restore_permissions, :restore_times, :size, :unix_gid, :unix_perms, :unix_uid, :zipfile @@ -720,11 +725,11 @@ def set_compression_level_flags case @compression_level when 1 - @gp_flags |= 0b110 + @gp_flags |= COMPRESSION_LEVEL_SUPERFAST_GPFLAG when 2 - @gp_flags |= 0b100 + @gp_flags |= COMPRESSION_LEVEL_FAST_GPFLAG when 8, 9 - @gp_flags |= 0b010 + @gp_flags |= COMPRESSION_LEVEL_MAX_GPFLAG end end From 2ff313da26c0936e526a1cc04862d25623da267f Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 13 Sep 2020 16:45:10 +0100 Subject: [PATCH 183/469] Update README instructions for setting compression_level. --- README.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 059f22d1..2d59eb7f 100644 --- a/README.md +++ b/README.md @@ -288,15 +288,25 @@ Zip.validate_entry_sizes = false Note that if you use the lower level `Zip::InputStream` interface, `rubyzip` does *not* check the entry `size`s. In this case, the caller is responsible for making sure it does not read more data than expected from the input stream. -### Default Compression +### Compression level -You can set the default compression level like so: +When adding entries to a zip archive you can set the compression level to trade-off compressed size against compression speed. By default this is set to the same as the underlying Zlib library's default (`Zlib::DEFAULT_COMPRESSION`), which is somewhere in the middle. + +You can configure the default compression level with: ```ruby -Zip.default_compression = Zlib::DEFAULT_COMPRESSION +Zip.default_compression = X ``` -It defaults to `Zlib::DEFAULT_COMPRESSION`. Possible values are `Zlib::BEST_COMPRESSION`, `Zlib::DEFAULT_COMPRESSION` and `Zlib::NO_COMPRESSION` +Where X is an integer between 0 and 9, inclusive. If this option is set to 0 (`Zlib::NO_COMPRESSION`) then entries will be stored in the zip archive uncompressed. A value of 1 (`Zlib::BEST_SPEED`) gives the fastest compression and 9 (`Zlib::BEST_COMPRESSION`) gives the smallest compressed file size. + +This can also be set for each archive as an option to `Zip::File`: + +```ruby +Zip::File.open('foo.zip', Zip::File::CREATE, {compression_level: 9}) do |zip| + zip.add ... +end +``` ### Zip64 Support From 8816279fe04db2f8a87655e19a9341778942bbae Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 13 Sep 2020 16:58:06 +0100 Subject: [PATCH 184/469] Update the 'Developing' instructions in the README. --- README.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2d59eb7f..f8958480 100644 --- a/README.md +++ b/README.md @@ -333,13 +333,22 @@ You can set multiple settings at the same time by using a block: ## Developing -To run the test you need to do this: +Install the dependencies: -``` +```shell bundle install +``` + +Run the tests with `rake`: + +```shell rake ``` +Please also run `rubocop` over your changes. + +Our CI is here: https://travis-ci.org/github/rubyzip/rubyzip. Please note that `rubocop` is run as part of the CI configuration and will fail a build if errors are found. + ## Website and Project Home http://github.com/rubyzip/rubyzip From 834ff70c4d6511dc85e3d4d198a4f7cdb09f655d Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 13 Sep 2020 17:13:02 +0100 Subject: [PATCH 185/469] Updated and cleaned-up authors in the README. --- README.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f8958480..fe1fd79d 100644 --- a/README.md +++ b/README.md @@ -357,15 +357,22 @@ http://rdoc.info/github/rubyzip/rubyzip/master/frames ## Authors -Alexander Simonov ( alex at simonov.me) +See https://github.com/rubyzip/rubyzip/graphs/contributors for a comprehensive list. -Alan Harper ( alan at aussiegeek.net) +### Current contributors -Thomas Sondergaard (thomas at sondergaard.cc) +* Robert Haines (@hainesr) +* John Lees-Miller (@jdleesmiller) -Technorama Ltd. (oss-ruby-zip at technorama.net) +### Past contributors -extra-field support contributed by Tatsuki Sugiura (sugi at nemui.org) +* Pavel Lobashov (@ShockwaveNN) +* Oleksandr Simonov (@simonoff) +* Alan Harper (@aussiegeek) + +### Original author + +* Thomas Sondergaard ## License From f0714137f6314a3c38a914c58e099f9ac1af1b82 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 13 Sep 2020 17:17:05 +0100 Subject: [PATCH 186/469] Update Changelog.md with an entry for this PR. --- Changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changelog.md b/Changelog.md index cb4a893e..ec6ae1fd 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,7 @@ # X.X.X (Next) +- Set compression level on a per-zipfile basis. [#448](https://github.com/rubyzip/rubyzip/pull/448) + Tooling: - Update rubocop again and run it in CI. [#444](https://github.com/rubyzip/rubyzip/pull/444) From b00c47a0471f64b379e2c1c5ece6a75875f42ec2 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 20 Sep 2020 14:41:24 +0100 Subject: [PATCH 187/469] Add a failing test for reading local extra fields. --- test/data/local_extra_field.zip | Bin 0 -> 9466 bytes test/extra_field_test.rb | 12 ++++++++++++ 2 files changed, 12 insertions(+) create mode 100644 test/data/local_extra_field.zip diff --git a/test/data/local_extra_field.zip b/test/data/local_extra_field.zip new file mode 100644 index 0000000000000000000000000000000000000000..5a936e4c351f0676c63eaeaf8bd39453ba5c2022 GIT binary patch literal 9466 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 MJpS`L()_pjUtRzq-T(jq literal 0 HcmV?d00001 diff --git a/test/extra_field_test.rb b/test/extra_field_test.rb index fa6e212d..69704890 100644 --- a/test/extra_field_test.rb +++ b/test/extra_field_test.rb @@ -73,4 +73,16 @@ def test_equality extra1.create('IUnix') assert_equal(extra1, extra3) end + + def test_read_local_extra_field + ::Zip::File.open('test/data/local_extra_field.zip') do |zf| + ['file1.txt', 'file2.txt'].each do |file| + entry = zf.get_entry(file) + + assert_instance_of(::Zip::ExtraField, entry.extra) + assert_equal(1_000, entry.extra['IUnix'].uid) + assert_equal(1_000, entry.extra['IUnix'].gid) + end + end + end end From fe1d3c8da0591a4a802f711b4a9770f34899132f Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 20 Sep 2020 15:06:04 +0100 Subject: [PATCH 188/469] Fix reading Ux extra field. As previously implemented the `uid` and `gid` fields could only ever be read as 0, because they were being initialized to zero and then memoization (`@uid ||= uid`) was used to 'save' the new value. Using `nil` as the initial value for either of these fields breaks so many tests, so I have fixed this by not using memoization instead. This is safe because it is only the local extra field that holds these values for this type of extra field. --- lib/zip/extra_field/unix.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/zip/extra_field/unix.rb b/lib/zip/extra_field/unix.rb index 9a66c81d..d83087e4 100644 --- a/lib/zip/extra_field/unix.rb +++ b/lib/zip/extra_field/unix.rb @@ -20,8 +20,8 @@ def merge(binstr) return if !size || size == 0 uid, gid = content.unpack('vv') - @uid ||= uid - @gid ||= gid # rubocop:disable Naming/MemoizedInstanceVariableName + @uid = uid + @gid = gid end def ==(other) From f742994cf25bd4c3fb66f69b9ecc012f3f51c214 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 20 Sep 2020 15:18:34 +0100 Subject: [PATCH 189/469] Abstract out reading extra fields in Entry. Remove some (almost) duplicated code and get ready for the real fix. --- lib/zip/entry.rb | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index a67c6568..bf47ad6d 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -271,12 +271,7 @@ def read_local_entry(io) #:nodoc:all raise ::Zip::Error, 'Truncated local zip entry header' end - if @extra.kind_of?(::Zip::ExtraField) - @extra.merge(extra) if extra - else - @extra = ::Zip::ExtraField.new(extra) - end - + read_extra_field(extra) parse_zip64_extra(true) @local_header_size = calculate_local_header_size end @@ -379,11 +374,11 @@ def check_c_dir_entry_comment_size raise ::Zip::Error, 'Truncated cdir zip entry header' end - def read_c_dir_extra_field(io) + def read_extra_field(buf) if @extra.kind_of?(::Zip::ExtraField) - @extra.merge(io.read(@extra_length)) + @extra.merge(buf) if buf else - @extra = ::Zip::ExtraField.new(io.read(@extra_length)) + @extra = ::Zip::ExtraField.new(buf) end end @@ -397,7 +392,7 @@ def read_c_dir_entry(io) #:nodoc:all if ::Zip.force_entry_names_encoding @name.force_encoding(::Zip.force_entry_names_encoding) end - read_c_dir_extra_field(io) + read_extra_field(io.read(@extra_length)) @comment = io.read(@comment_length) check_c_dir_entry_comment_size set_ftype_from_c_dir_entry From a9628ef9d5ee9f85d7559a658732964fca2a8580 Mon Sep 17 00:00:00 2001 From: Kentaro Hayashi Date: Fri, 25 Sep 2020 16:17:01 +0900 Subject: [PATCH 190/469] Use correct SPDX license identifier The valid SPDX license is "BSD-2-Clause" instead of "BSD 2-Clause". ref. https://spdx.org/licenses/ --- rubyzip.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rubyzip.gemspec b/rubyzip.gemspec index 24e9ecf3..67de78dc 100644 --- a/rubyzip.gemspec +++ b/rubyzip.gemspec @@ -12,7 +12,7 @@ Gem::Specification.new do |s| s.summary = 'rubyzip is a ruby module for reading and writing zip files' s.files = Dir.glob('{samples,lib}/**/*.rb') + %w[README.md TODO Rakefile] s.require_paths = ['lib'] - s.license = 'BSD 2-Clause' + s.license = 'BSD-2-Clause' s.metadata = { 'bug_tracker_uri' => 'https://github.com/rubyzip/rubyzip/issues', 'changelog_uri' => "https://github.com/rubyzip/rubyzip/blob/v#{s.version}/Changelog.md", From c2b9aa2893c03537e9a985b8410d9bb1eeeba8e2 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 20 Sep 2020 18:11:49 +0100 Subject: [PATCH 191/469] Correctly read extra fields when opening a zip file. Previously, only the extra fields stored in the central directory were being read in. In reality it is often the case that the extra field in the central directory is just a marker, and the full data is in the local header. So we need to read both in and merge the two into the final correct extra field. This merging infrastructure was already implemented in the extra field code but we never actually read the local extra fields in until now. Reading the central directory headers and local entry headers seems rather fragile, so we can't just read one over the other and hope to end up with a correctly merged set of extra fields because this breaks other things. So we need to specifically read the local extra field data and merge just those bits. This commit also fixes a couple of tests that were 'broken' by us now reading extra fields in correctly! --- lib/zip/central_directory.rb | 29 +++++++++++++++++++++++- test/filesystem/file_nonmutating_test.rb | 6 +++-- test/filesystem/file_stat_test.rb | 4 ++-- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/lib/zip/central_directory.rb b/lib/zip/central_directory.rb index 9975884c..5b64c7f5 100644 --- a/lib/zip/central_directory.rb +++ b/lib/zip/central_directory.rb @@ -124,10 +124,37 @@ def read_central_directory_entries(io) #:nodoc: end @entry_set = EntrySet.new @size.times do - @entry_set << Entry.read_c_dir_entry(io) + entry = Entry.read_c_dir_entry(io) + next unless entry + + offset = if entry.extra['Zip64'] + entry.extra['Zip64'].relative_header_offset + else + entry.local_header_offset + end + + unless offset.nil? + io_save = io.tell + io.seek(offset, IO::SEEK_SET) + entry.read_extra_field(read_local_extra_field(io)) + io.seek(io_save, IO::SEEK_SET) + end + + @entry_set << entry end end + def read_local_extra_field(io) + buf = io.read(::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH) || '' + return '' unless buf.bytesize == ::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH + + head, _, _, _, _, _, _, _, _, _, n_len, e_len = buf.unpack('VCCvvvvVVVvv') + return '' unless head == ::Zip::LOCAL_ENTRY_SIGNATURE + + io.seek(n_len, IO::SEEK_CUR) # Skip over the entry name. + io.read(e_len) + end + def read_from_stream(io) #:nodoc: buf = start_buf(io) if zip64_file?(buf) diff --git a/test/filesystem/file_nonmutating_test.rb b/test/filesystem/file_nonmutating_test.rb index 346d5a76..485298fa 100644 --- a/test/filesystem/file_nonmutating_test.rb +++ b/test/filesystem/file_nonmutating_test.rb @@ -295,8 +295,10 @@ def test_ctime end def test_atime - assert_nil(@zip_file.file.atime('file1')) - assert_nil(@zip_file.file.stat('file1').atime) + assert_equal(::Zip::DOSTime.at(1_027_694_306), + @zip_file.file.atime('file1')) + assert_equal(::Zip::DOSTime.at(1_027_694_306), + @zip_file.file.stat('file1').atime) end def test_ntfs_time diff --git a/test/filesystem/file_stat_test.rb b/test/filesystem/file_stat_test.rb index 05d7fff8..b8efe754 100644 --- a/test/filesystem/file_stat_test.rb +++ b/test/filesystem/file_stat_test.rb @@ -19,11 +19,11 @@ def test_ino end def test_uid - assert_equal(0, @zip_file.file.stat('file1').uid) + assert_equal(500, @zip_file.file.stat('file1').uid) end def test_gid - assert_equal(0, @zip_file.file.stat('file1').gid) + assert_equal(500, @zip_file.file.stat('file1').gid) end def test_ftype From a668fd14d2047b27ba9fa5d39e7e7f91971a4084 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 21 Sep 2020 09:34:14 +0100 Subject: [PATCH 192/469] Test reading an extra field with a bad header ID. --- test/extra_field_test.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/extra_field_test.rb b/test/extra_field_test.rb index 69704890..6f2ec65b 100644 --- a/test/extra_field_test.rb +++ b/test/extra_field_test.rb @@ -17,6 +17,16 @@ def test_unknownfield assert_equal(extra.to_s, 'fooabarbaz') end + def test_bad_header_id + str = "ut\x5\0\x3\250$\r@" + ut = nil + assert_output('', /WARNING/) do + ut = ::Zip::ExtraField::UniversalTime.new(str) + end + assert_instance_of(::Zip::ExtraField::UniversalTime, ut) + assert_nil(ut.mtime) + end + def test_ntfs str = "\x0A\x00 \x00\x00\x00\x00\x00\x01\x00\x18\x00\xC0\x81\x17\xE8B\xCE\xCF\x01\xC0\x81\x17\xE8B\xCE\xCF\x01\xC0\x81\x17\xE8B\xCE\xCF\x01" extra = ::Zip::ExtraField.new(str) From 8bafcbbc4db64ba7e9f6d7e2fc59cb3ac87dc685 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 21 Sep 2020 09:51:47 +0100 Subject: [PATCH 193/469] Remove dead code in extra_field/generic.rb (`==`). From what I can tell this was erroneously copied out of extra_field.rb during a refactor. It attempts to compare a non-existent Hash that used to be inherited before the refactor. If this code had been left within ExtraField it would make more sense, but as it's not needed there either let's just remove it. See 20d79feb995728c6b791580cc82f62f2be82606e for the refactor. --- lib/zip/extra_field/generic.rb | 9 --------- 1 file changed, 9 deletions(-) diff --git a/lib/zip/extra_field/generic.rb b/lib/zip/extra_field/generic.rb index 5eb702d6..9237a1db 100644 --- a/lib/zip/extra_field/generic.rb +++ b/lib/zip/extra_field/generic.rb @@ -22,15 +22,6 @@ def initial_parse(binstr) [binstr[2, 2].unpack1('v'), binstr[4..-1]] end - def ==(other) - return false if self.class != other.class - - each do |k, v| - return false if v != other[k] - end - true - end - def to_local_bin s = pack_for_local self.class.const_get(:HEADER_ID) + [s.bytesize].pack('v') << s From 0235e76baecd1d952e44319db906775156675664 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 21 Sep 2020 10:50:22 +0100 Subject: [PATCH 194/469] Test packing the NTFS extra field. --- test/extra_field_test.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/extra_field_test.rb b/test/extra_field_test.rb index 6f2ec65b..52140a2d 100644 --- a/test/extra_field_test.rb +++ b/test/extra_field_test.rb @@ -35,6 +35,8 @@ def test_ntfs assert_equal(t, extra['NTFS'].mtime) assert_equal(t, extra['NTFS'].atime) assert_equal(t, extra['NTFS'].ctime) + + assert_equal(str.force_encoding('BINARY'), extra.to_local_bin) end def test_merge From ac89366902df4dff7ea4d2384d565787f5c08b02 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 8 Nov 2020 17:19:49 +0000 Subject: [PATCH 195/469] Failing test to catch error on read after readline. --- test/input_stream_test.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/input_stream_test.rb b/test/input_stream_test.rb index 773ee6b5..34042ba9 100644 --- a/test/input_stream_test.rb +++ b/test/input_stream_test.rb @@ -179,4 +179,14 @@ def test_ungetc assert_equal('$VERBOSE =', zis.read(10)) end end + + def test_readline_then_read + ::Zip::InputStream.open(TestZipFile::TEST_ZIP2.zip_name) do |zis| + zis.get_next_entry + assert_equal("#!/usr/bin/env ruby\n", zis.readline) + refute(zis.eof?) + refute_empty(zis.read) # Also should not raise an error. + assert(zis.eof?) + end + end end From 2ea805c9513e5a1976b17c309ccdcbd15a4f71f0 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 8 Nov 2020 17:20:53 +0000 Subject: [PATCH 196/469] Check `number_of_bytes` before comparison in read. If an input stream has been read from, and left some data in the internal buffer, then a subsequent `read`, with no amount of bytes to be read having been specified, will raise an error when comparing to `nil`. This fix checks that the number of bytes specified in the `read` is not `nil` before comparing with the size of the internal buffer. Fixes #461. --- lib/zip/ioextras/abstract_input_stream.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zip/ioextras/abstract_input_stream.rb b/lib/zip/ioextras/abstract_input_stream.rb index 8392d240..848dcae3 100644 --- a/lib/zip/ioextras/abstract_input_stream.rb +++ b/lib/zip/ioextras/abstract_input_stream.rb @@ -19,7 +19,7 @@ def initialize def read(number_of_bytes = nil, buf = '') tbuf = if @output_buffer.bytesize > 0 - if number_of_bytes <= @output_buffer.bytesize + if number_of_bytes && number_of_bytes <= @output_buffer.bytesize @output_buffer.slice!(0, number_of_bytes) else number_of_bytes -= @output_buffer.bytesize if number_of_bytes From 0a6037b0ad067c8a90021609c86f42c6963c4566 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 8 Nov 2020 17:51:57 +0000 Subject: [PATCH 197/469] Update Changelog.md. --- Changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changelog.md b/Changelog.md index cb4a893e..ab862d3f 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,7 @@ # X.X.X (Next) +- Fix input stream partial read error. [#462](https://github.com/rubyzip/rubyzip/pull/462) + Tooling: - Update rubocop again and run it in CI. [#444](https://github.com/rubyzip/rubyzip/pull/444) From 5a4d1d8b6b9b556d05114ee8f005b20579e0cf8a Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 28 Nov 2020 21:19:58 +0000 Subject: [PATCH 198/469] Replace and deprecate `Zip::DOSTime#dos_equals`. Having a specific 'does this instance equal another instance' method is kind of annoying and breaks a number of things. Most obviously it breaks comparing to `nil`: `nil.dos_equals(other)` will fail where `nil == other` does not. So this commit overrides `<=>` in `Zip::DOSTime` and deprecates `dos_equals`. --- lib/zip/dos_time.rb | 9 +++++++-- lib/zip/entry.rb | 3 +-- test/output_stream_test.rb | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/zip/dos_time.rb b/lib/zip/dos_time.rb index 1d77aa40..326645ad 100644 --- a/lib/zip/dos_time.rb +++ b/lib/zip/dos_time.rb @@ -24,9 +24,14 @@ def to_binary_dos_date ((year - 1980) << 9) end - # Dos time is only stored with two seconds accuracy def dos_equals(other) - to_i / 2 == other.to_i / 2 + warn 'Zip::DOSTime#dos_equals is deprecated. Use `==` instead.' + self == other + end + + # Dos time is only stored with two seconds accuracy. + def <=>(other) + (to_i / 2) <=> (other.to_i / 2) end # Create a DOSTime instance from a vanilla Time instance. diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index a67c6568..783a3b17 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -503,10 +503,9 @@ def ==(other) return false unless other.class == self.class # Compares contents of local entry and exposed fields - keys_equal = %w[compression_method crc compressed_size size name extra filepath].all? do |k| + %w[compression_method crc compressed_size size name extra filepath time].all? do |k| other.__send__(k.to_sym) == __send__(k.to_sym) end - keys_equal && time.dos_equals(other.time) end def <=>(other) diff --git a/test/output_stream_test.rb b/test/output_stream_test.rb index b2f64ab9..f86eb2ec 100644 --- a/test/output_stream_test.rb +++ b/test/output_stream_test.rb @@ -100,7 +100,7 @@ def test_put_next_entry_using_zip_entry_creates_entries_with_correct_timestamps ::Zip::InputStream.open(TEST_ZIP.zip_name) do |io| while (entry = io.get_next_entry) # Compare DOS Times, since they are stored with two seconds accuracy - assert(::Zip::DOSTime.at(file.mtime).dos_equals(::Zip::DOSTime.at(entry.mtime))) + assert(::Zip::DOSTime.at(file.mtime) == ::Zip::DOSTime.at(entry.mtime)) end end end From 4dc308caed51193b82edee4b2d118ac266cef866 Mon Sep 17 00:00:00 2001 From: Brian Buchalter Date: Tue, 26 Jan 2021 04:34:45 -0700 Subject: [PATCH 199/469] Prefer OUTPUT_FIELD_SEPARATOR to $, --- test/ioextras/abstract_output_stream_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/ioextras/abstract_output_stream_test.rb b/test/ioextras/abstract_output_stream_test.rb index 9b02309c..50470a4b 100644 --- a/test/ioextras/abstract_output_stream_test.rb +++ b/test/ioextras/abstract_output_stream_test.rb @@ -25,7 +25,7 @@ def setup end def teardown - $, = @save_comma_sep + $OUTPUT_FIELD_SEPARATOR = @save_comma_sep $\ = @save_output_sep end @@ -57,7 +57,7 @@ def test_print @output_stream.print('I sure hope so!') assert_equal("hello world. You ok out there?\nI sure hope so!\n", @output_stream.buffer) - $, = 'X' + $OUTPUT_FIELD_SEPARATOR = '\n' @output_stream.buffer = '' @output_stream.print('monkey', 'duck', 'zebra') assert_equal("monkeyXduckXzebra\n", @output_stream.buffer) From adfd15a1c658fd84956d6c9f925ddbf91b0b2fb5 Mon Sep 17 00:00:00 2001 From: Brian Buchalter Date: Tue, 26 Jan 2021 04:35:21 -0700 Subject: [PATCH 200/469] Prefer OUTPUT_RECORD_SEPARATOR to $\ --- test/ioextras/abstract_output_stream_test.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/ioextras/abstract_output_stream_test.rb b/test/ioextras/abstract_output_stream_test.rb index 50470a4b..1871b5f4 100644 --- a/test/ioextras/abstract_output_stream_test.rb +++ b/test/ioextras/abstract_output_stream_test.rb @@ -26,7 +26,7 @@ def setup def teardown $OUTPUT_FIELD_SEPARATOR = @save_comma_sep - $\ = @save_output_sep + $OUTPUT_RECORD_SEPARATOR = @save_output_sep end def test_write @@ -40,7 +40,7 @@ def test_write end def test_print - $\ = nil # record separator set to nil + $OUTPUT_RECORD_SEPARATOR = nil # record separator set to nil @output_stream.print('hello') assert_equal('hello', @output_stream.buffer) @@ -50,7 +50,7 @@ def test_print @output_stream.print(' You ok ', 'out ', 'there?') assert_equal('hello world. You ok out there?', @output_stream.buffer) - $\ = "\n" + $OUTPUT_RECORD_SEPARATOR = "\n" @output_stream.print assert_equal("hello world. You ok out there?\n", @output_stream.buffer) @@ -62,7 +62,7 @@ def test_print @output_stream.print('monkey', 'duck', 'zebra') assert_equal("monkeyXduckXzebra\n", @output_stream.buffer) - $\ = nil + $OUTPUT_RECORD_SEPARATOR = nil @output_stream.buffer = '' @output_stream.print(20) assert_equal('20', @output_stream.buffer) From 0585e4e36bb4e8f3f78b296a6c90dcb6ceda105d Mon Sep 17 00:00:00 2001 From: Brian Buchalter Date: Tue, 26 Jan 2021 04:36:13 -0700 Subject: [PATCH 201/469] Set OUTPUT_FIELD_SEPARATOR to nil in test As discussed in https://bugs.ruby-lang.org/issues/14240 and implemented in https://github.com/ruby/ruby/commit/6298ec2875a6f1a1e75698c96ceac94362f20bcf setting OUTPUT_FIELD_SEPERATOR to a non-nil value is now depricated. --- test/ioextras/abstract_output_stream_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/ioextras/abstract_output_stream_test.rb b/test/ioextras/abstract_output_stream_test.rb index 1871b5f4..e517a473 100644 --- a/test/ioextras/abstract_output_stream_test.rb +++ b/test/ioextras/abstract_output_stream_test.rb @@ -57,10 +57,10 @@ def test_print @output_stream.print('I sure hope so!') assert_equal("hello world. You ok out there?\nI sure hope so!\n", @output_stream.buffer) - $OUTPUT_FIELD_SEPARATOR = '\n' + $OUTPUT_FIELD_SEPARATOR = nil @output_stream.buffer = '' @output_stream.print('monkey', 'duck', 'zebra') - assert_equal("monkeyXduckXzebra\n", @output_stream.buffer) + assert_equal("monkeyduckzebra\n", @output_stream.buffer) $OUTPUT_RECORD_SEPARATOR = nil @output_stream.buffer = '' From 68b9ed4cfe964cbad46c31d8c5010aed07213431 Mon Sep 17 00:00:00 2001 From: Brian Buchalter Date: Tue, 26 Jan 2021 04:41:51 -0700 Subject: [PATCH 202/469] Remove OUTPUT_FIELD_SEPARATOR-related test behaviors Since modifying OUTPUT_FIELD_SEPARATOR is deprecated, there's no need for us to do this in our test. --- test/ioextras/abstract_output_stream_test.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/ioextras/abstract_output_stream_test.rb b/test/ioextras/abstract_output_stream_test.rb index e517a473..c6117aa7 100644 --- a/test/ioextras/abstract_output_stream_test.rb +++ b/test/ioextras/abstract_output_stream_test.rb @@ -20,12 +20,10 @@ def <<(data) def setup @output_stream = TestOutputStream.new - @save_comma_sep = $OUTPUT_FIELD_SEPARATOR @save_output_sep = $OUTPUT_RECORD_SEPARATOR end def teardown - $OUTPUT_FIELD_SEPARATOR = @save_comma_sep $OUTPUT_RECORD_SEPARATOR = @save_output_sep end @@ -57,7 +55,6 @@ def test_print @output_stream.print('I sure hope so!') assert_equal("hello world. You ok out there?\nI sure hope so!\n", @output_stream.buffer) - $OUTPUT_FIELD_SEPARATOR = nil @output_stream.buffer = '' @output_stream.print('monkey', 'duck', 'zebra') assert_equal("monkeyduckzebra\n", @output_stream.buffer) From ab9f546557fd53621907f0204a1462a88127ac1b Mon Sep 17 00:00:00 2001 From: Brian Buchalter Date: Tue, 26 Jan 2021 04:43:26 -0700 Subject: [PATCH 203/469] Use default ruby behavior for Array.join Since we are using the default behavior of OUTPUT_FIELD_SEPERATOR anyway, let's allow Ruby to maange that default for us, so if it should change in the future, we don't have to change. --- lib/zip/ioextras/abstract_output_stream.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zip/ioextras/abstract_output_stream.rb b/lib/zip/ioextras/abstract_output_stream.rb index b94c9d49..7c0c5682 100644 --- a/lib/zip/ioextras/abstract_output_stream.rb +++ b/lib/zip/ioextras/abstract_output_stream.rb @@ -11,7 +11,7 @@ def write(data) end def print(*params) - self << params.join($OUTPUT_FIELD_SEPARATOR) << $OUTPUT_RECORD_SEPARATOR.to_s + self << params.join << $OUTPUT_RECORD_SEPARATOR.to_s end def printf(a_format_string, *params) From 72cedd7ce4993dcdc5c4c115e67ab00a627b51f9 Mon Sep 17 00:00:00 2001 From: Brian Buchalter Date: Tue, 26 Jan 2021 06:15:14 -0700 Subject: [PATCH 204/469] Remove compare_enumerables from test_helper.rb This change has several benefits: * When errors occur, the test provides useful feedback, showing you expected vs. actual. * We no longer need to open and modify the Enumerable module. * The test is more readable. --- test/central_directory_test.rb | 4 +--- test/test_helper.rb | 10 ---------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/test/central_directory_test.rb b/test/central_directory_test.rb index c4f7afa0..00c618a5 100644 --- a/test/central_directory_test.rb +++ b/test/central_directory_test.rb @@ -10,9 +10,7 @@ def test_read_from_stream cdir = ::Zip::CentralDirectory.read_from_stream(zip_file) assert_equal(TestZipFile::TEST_ZIP2.entry_names.size, cdir.size) - assert(cdir.entries.sort.compare_enumerables(TestZipFile::TEST_ZIP2.entry_names.sort) do |cdir_entry, test_entry_name| - cdir_entry.name == test_entry_name - end) + assert_equal(cdir.entries.map(&:name).sort, TestZipFile::TEST_ZIP2.entry_names.sort) assert_equal(TestZipFile::TEST_ZIP2.comment, cdir.comment) end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 598736e6..eb3e13bc 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -165,16 +165,6 @@ def run_crc_test(compressor_class) end end -module Enumerable - def compare_enumerables(enumerable) - array = enumerable.to_a - each_with_index do |element, index| - return false unless yield(element, array[index]) - end - size == array.size - end -end - module CommonZipFileFixture include AssertEntry From 2c4de67d9f12c4bdeedae5da97c7a86a854d8143 Mon Sep 17 00:00:00 2001 From: Brian Buchalter Date: Tue, 26 Jan 2021 07:54:23 -0700 Subject: [PATCH 205/469] Simplify assertions in basic_zip_file_test We can get the same strength of assertions with less code. Also, by using assert_equal instead of assert, we get better feedback when assertion does not meet expectations. --- test/basic_zip_file_test.rb | 43 +++++++++++++++---------------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/test/basic_zip_file_test.rb b/test/basic_zip_file_test.rb index 994728a3..8ff999bb 100644 --- a/test/basic_zip_file_test.rb +++ b/test/basic_zip_file_test.rb @@ -8,44 +8,35 @@ def setup end def test_entries - assert_equal(TestZipFile::TEST_ZIP2.entry_names.sort, - @zip_file.entries.entries.sort.map(&:name)) + expected_entry_names = TestZipFile::TEST_ZIP2.entry_names + actual_entry_names = @zip_file.entries.entries.map(&:name) + assert_equal(expected_entry_names.sort, actual_entry_names.sort) end def test_each - count = 0 - visited = {} - @zip_file.each do |entry| - assert(TestZipFile::TEST_ZIP2.entry_names.include?(entry.name)) - assert(!visited.include?(entry.name)) - visited[entry.name] = nil - count = count.succ - end - assert_equal(TestZipFile::TEST_ZIP2.entry_names.length, count) + expected_entry_names = TestZipFile::TEST_ZIP2.entry_names + actual_entry_names = [] + @zip_file.each { |entry| actual_entry_names << entry.name } + assert_equal(expected_entry_names.sort, actual_entry_names.sort) end def test_foreach - count = 0 - visited = {} - ::Zip::File.foreach(TestZipFile::TEST_ZIP2.zip_name) do |entry| - assert(TestZipFile::TEST_ZIP2.entry_names.include?(entry.name)) - assert(!visited.include?(entry.name)) - visited[entry.name] = nil - count = count.succ - end - assert_equal(TestZipFile::TEST_ZIP2.entry_names.length, count) + expected_entry_names = TestZipFile::TEST_ZIP2.entry_names + actual_entry_names = [] + ::Zip::File.foreach(TestZipFile::TEST_ZIP2.zip_name) { |entry| actual_entry_names << entry.name } + assert_equal(expected_entry_names.sort, actual_entry_names.sort) end def test_get_input_stream - count = 0 - visited = {} + expected_entry_names = TestZipFile::TEST_ZIP2.entry_names + actual_entry_names = [] + @zip_file.each do |entry| + actual_entry_names << entry.name assert_entry(entry.name, @zip_file.get_input_stream(entry), entry.name) - assert(!visited.include?(entry.name)) - visited[entry.name] = nil - count = count.succ end - assert_equal(TestZipFile::TEST_ZIP2.entry_names.length, count) + + assert_equal(expected_entry_names.sort, actual_entry_names.sort) end def test_get_input_stream_block From 9da6be98d827cb35808974f94311bd87e2f54618 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 16 Feb 2021 13:21:24 +0000 Subject: [PATCH 206/469] Fix the compression level tests to be relative. Made little sense to use hardcoded bytes sizes; the tests end up too brittle. --- test/file_test.rb | 130 +++++++++++++++++++--------------------------- 1 file changed, 53 insertions(+), 77 deletions(-) diff --git a/test/file_test.rb b/test/file_test.rb index 6c52adfc..f283a884 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -192,95 +192,71 @@ def test_cleans_up_tempfiles_after_close assert_equal(false, File.exist?(@tempfile_path)) end - def test_add_default_compression + def test_add_different_compression src_file = 'test/data/file2.txt' entry_name = 'newEntryName.rb' - assert(::File.exist?(src_file)) - zf = ::Zip::File.new(EMPTY_FILENAME, ::Zip::File::CREATE) - zf.add(entry_name, src_file) - zf.close - - zf_read = ::Zip::File.new(EMPTY_FILENAME) - entry = zf_read.entries.first - assert_equal('', zf_read.comment) - assert_equal(1, zf_read.entries.length) - assert_equal(entry_name, zf_read.entries.first.name) - assert_equal(File.size(src_file), entry.size) - assert_equal(8_764, entry.compressed_size) - AssertEntry.assert_contents(src_file, - zf_read.get_input_stream(entry_name, &:read)) - end - - def test_add_best_compression - src_file = 'test/data/file2.txt' - entry_name = 'newEntryName.rb' - assert(::File.exist?(src_file)) - zf = ::Zip::File.new(EMPTY_FILENAME, ::Zip::File::CREATE, false, { compression_level: Zlib::BEST_COMPRESSION }) - zf.add(entry_name, src_file) - zf.close - - zf_read = ::Zip::File.new(EMPTY_FILENAME) - entry = zf_read.entries.first - assert_equal(1, zf_read.entries.length) - assert_equal(File.size(src_file), entry.size) - assert_equal(8_658, entry.compressed_size) - AssertEntry.assert_contents(src_file, - zf_read.get_input_stream(entry_name, &:read)) - end + files = [ + ['test/data/fast_comp.zip', Zlib::BEST_SPEED], + ['test/data/default_comp.zip', Zlib::DEFAULT_COMPRESSION], + ['test/data/best_comp.zip', Zlib::BEST_COMPRESSION] + ] + sizes = [] + + files.each do |name, comp| + zf = ::Zip::File.new( + name, ::Zip::File::CREATE, false, { compression_level: comp } + ) + + zf.add(entry_name, src_file) + zf.close - def test_add_best_compression_as_default - ::Zip.default_compression = Zlib::BEST_COMPRESSION + zf_read = ::Zip::File.new(name) + entry = zf_read.entries.first + assert_equal(File.size(src_file), entry.size) + AssertEntry.assert_contents( + src_file, zf_read.get_input_stream(entry.name, &:read) + ) + sizes << entry.compressed_size + zf_read.close - src_file = 'test/data/file2.txt' - entry_name = 'newEntryName.rb' - assert(::File.exist?(src_file)) - zf = ::Zip::File.new(EMPTY_FILENAME, ::Zip::File::CREATE) - zf.add(entry_name, src_file) - zf.close + ::File.delete(name) + end - zf_read = ::Zip::File.new(EMPTY_FILENAME) - entry = zf_read.entries.first - assert_equal(1, zf_read.entries.length) - assert_equal(File.size(src_file), entry.size) - assert_equal(8_658, entry.compressed_size) - AssertEntry.assert_contents(src_file, - zf_read.get_input_stream(entry_name, &:read)) + assert(sizes[0] > sizes[1]) + assert(sizes[1] > sizes[2]) end - def test_add_best_speed + def test_add_different_compression_as_default src_file = 'test/data/file2.txt' entry_name = 'newEntryName.rb' - assert(::File.exist?(src_file)) - zf = ::Zip::File.new(EMPTY_FILENAME, ::Zip::File::CREATE, false, { compression_level: Zlib::BEST_SPEED }) - zf.add(entry_name, src_file) - zf.close - - zf_read = ::Zip::File.new(EMPTY_FILENAME) - entry = zf_read.entries.first - assert_equal(1, zf_read.entries.length) - assert_equal(File.size(src_file), entry.size) - assert_equal(10_938, entry.compressed_size) - AssertEntry.assert_contents(src_file, - zf_read.get_input_stream(entry_name, &:read)) - end + files = [ + ['test/data/fast_comp.zip', Zlib::BEST_SPEED], + ['test/data/default_comp.zip', Zlib::DEFAULT_COMPRESSION], + ['test/data/best_comp.zip', Zlib::BEST_COMPRESSION] + ] + sizes = [] + + files.each do |name, comp| + ::Zip.default_compression = comp + zf = ::Zip::File.new(name, ::Zip::File::CREATE) + + zf.add(entry_name, src_file) + zf.close - def test_add_best_speed_as_default - ::Zip.default_compression = Zlib::BEST_SPEED + zf_read = ::Zip::File.new(name) + entry = zf_read.entries.first + assert_equal(File.size(src_file), entry.size) + AssertEntry.assert_contents( + src_file, zf_read.get_input_stream(entry.name, &:read) + ) + sizes << entry.compressed_size + zf_read.close - src_file = 'test/data/file2.txt' - entry_name = 'newEntryName.rb' - assert(::File.exist?(src_file)) - zf = ::Zip::File.new(EMPTY_FILENAME, ::Zip::File::CREATE) - zf.add(entry_name, src_file) - zf.close + ::File.delete(name) + end - zf_read = ::Zip::File.new(EMPTY_FILENAME) - entry = zf_read.entries.first - assert_equal(1, zf_read.entries.length) - assert_equal(File.size(src_file), entry.size) - assert_equal(10_938, entry.compressed_size) - AssertEntry.assert_contents(src_file, - zf_read.get_input_stream(entry_name, &:read)) + assert(sizes[0] > sizes[1]) + assert(sizes[1] > sizes[2]) end def test_add_stored From 6b656d3277885afbecd18a67b2ed6a56dfb4d461 Mon Sep 17 00:00:00 2001 From: Taichi Ishitani Date: Sat, 6 Mar 2021 00:11:47 +0900 Subject: [PATCH 207/469] add Ruby 3.0 to CI --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index c73a251b..231e70c8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ rvm: - 2.5 - 2.6 - 2.7 + - 3.0 - ruby-head - truffleruby-head - truffleruby From fd510e07eb41919371af8459921d5b1199e41f5d Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 16 May 2021 19:43:27 +0100 Subject: [PATCH 208/469] Add a GitHub action for linting. --- .github/workflows/lint.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/workflows/lint.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..fc6b09bd --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,15 @@ +name: Linter + +on: [push, pull_request] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.4 + bundler-cache: true + - name: Rubocop + run: bundle exec rubocop From 25ce623d139498a21454d8e04561c6e038c86c43 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 16 May 2021 21:36:04 +0100 Subject: [PATCH 209/469] Add a GitHub action for CI tests. --- .github/workflows/tests.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..8dea992f --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,17 @@ +name: Tests + +on: [push, pull_request] + +jobs: + test: + strategy: + matrix: + ruby: [2.4, 2.5, 2.6, 2.7, '3.0', head, jruby, truffleruby] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true + - run: bundle exec rake From e24f191222b64f405ba37c24e794f179390b49d8 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 16 May 2021 21:47:49 +0100 Subject: [PATCH 210/469] Add some head versions to CI action and allow errors. --- .github/workflows/tests.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8dea992f..36238058 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,8 +6,17 @@ jobs: test: strategy: matrix: - ruby: [2.4, 2.5, 2.6, 2.7, '3.0', head, jruby, truffleruby] + ruby: [2.4, 2.5, 2.6, 2.7, '3.0', jruby, truffleruby] + can-fail: [false] + include: + - ruby: head + can-fail: true + - ruby: jruby-head + can-fail: true + - ruby: truffleruby-head + can-fail: true runs-on: ubuntu-latest + continue-on-error: ${{ matrix.can-fail }} steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 From 6536b964583232dc13debde49186cd38590eae8b Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 17 May 2021 12:38:46 +0100 Subject: [PATCH 211/469] Turn off fail-fast for the CI tests action. --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 36238058..7760ad69 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -5,6 +5,7 @@ on: [push, pull_request] jobs: test: strategy: + fail-fast: false matrix: ruby: [2.4, 2.5, 2.6, 2.7, '3.0', jruby, truffleruby] can-fail: [false] From 65886ac875cf060bddc535faf1d741fa86cb261e Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 17 May 2021 12:49:49 +0100 Subject: [PATCH 212/469] Add GitHub actions badges to the README. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index fe1fd79d..a2f59025 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # rubyzip [![Gem Version](https://badge.fury.io/rb/rubyzip.svg)](http://badge.fury.io/rb/rubyzip) +[![Tests](https://github.com/rubyzip/rubyzip/actions/workflows/tests.yml/badge.svg)](https://github.com/rubyzip/rubyzip/actions/workflows/tests.yml) +[![Linter](https://github.com/rubyzip/rubyzip/actions/workflows/lint.yml/badge.svg)](https://github.com/rubyzip/rubyzip/actions/workflows/lint.yml) [![Build Status](https://secure.travis-ci.org/rubyzip/rubyzip.svg)](http://travis-ci.org/rubyzip/rubyzip) [![Code Climate](https://codeclimate.com/github/rubyzip/rubyzip.svg)](https://codeclimate.com/github/rubyzip/rubyzip) [![Coverage Status](https://img.shields.io/coveralls/rubyzip/rubyzip.svg)](https://coveralls.io/r/rubyzip/rubyzip?branch=master) From 6e9f2976d165ab33621a1f65d223172871c8ac81 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 16 May 2021 16:47:12 +0100 Subject: [PATCH 213/469] Add temporary fix for JRuby to workaround Time cmp bug. Workaround jruby/jruby#6668 until fix is released. Version 9.2.18.0 is hopefully the version that will fix this, but we can adjust the version accordingly if not. --- lib/zip/dos_time.rb | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/lib/zip/dos_time.rb b/lib/zip/dos_time.rb index 326645ad..54907a21 100644 --- a/lib/zip/dos_time.rb +++ b/lib/zip/dos_time.rb @@ -1,3 +1,5 @@ +require 'rubygems' + module Zip class DOSTime < Time #:nodoc:all # MS-DOS File Date and Time format as used in Interrupt 21H Function 57H: @@ -50,6 +52,32 @@ def self.parse_binary_dos_format(bin_dos_date, bin_dos_time) local(year, month, day, hour, minute, second) end end + + if defined? JRUBY_VERSION && Gem::Version.new(JRUBY_VERSION) < '9.2.18.0' + module JRubyCMP # :nodoc: + def ==(other) + (self <=> other).zero? + end + + def <(other) + (self <=> other).negative? + end + + def <=(other) + (self <=> other) <= 0 + end + + def >(other) + (self <=> other).positive? + end + + def >=(other) + (self <=> other) >= 0 + end + end + + include JRubyCMP + end end end From 4ed35cae9435307cff5ef3558ce8ce7880f46212 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 17 May 2021 19:57:16 +0100 Subject: [PATCH 214/469] DosTime#<=> should return `nil` if other is not comparable. --- lib/zip/dos_time.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/zip/dos_time.rb b/lib/zip/dos_time.rb index 54907a21..51139dde 100644 --- a/lib/zip/dos_time.rb +++ b/lib/zip/dos_time.rb @@ -33,6 +33,8 @@ def dos_equals(other) # Dos time is only stored with two seconds accuracy. def <=>(other) + return unless other.kind_of?(Time) + (to_i / 2) <=> (other.to_i / 2) end From 5c8bb1c4a062e3869d23fe064d29c1a2e8d8dbc7 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 17 May 2021 20:21:00 +0100 Subject: [PATCH 215/469] Update CI information in the README. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a2f59025..75763bc3 100644 --- a/README.md +++ b/README.md @@ -349,7 +349,7 @@ rake Please also run `rubocop` over your changes. -Our CI is here: https://travis-ci.org/github/rubyzip/rubyzip. Please note that `rubocop` is run as part of the CI configuration and will fail a build if errors are found. +Our CI currently runs on [Travis](https://travis-ci.org/github/rubyzip/rubyzip) and [GitHub Actions](https://github.com/rubyzip/rubyzip/actions). Please note that `rubocop` is run as part of the CI configuration and will fail a build if errors are found. ## Website and Project Home From 1f5aa847389ba189112a3652ed94a8dad74931dd Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 17 May 2021 20:21:24 +0100 Subject: [PATCH 216/469] Update maintainer information in the README. --- README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/README.md b/README.md index 75763bc3..f465ff3b 100644 --- a/README.md +++ b/README.md @@ -361,16 +361,11 @@ http://rdoc.info/github/rubyzip/rubyzip/master/frames See https://github.com/rubyzip/rubyzip/graphs/contributors for a comprehensive list. -### Current contributors +### Current maintainers * Robert Haines (@hainesr) * John Lees-Miller (@jdleesmiller) - -### Past contributors - -* Pavel Lobashov (@ShockwaveNN) * Oleksandr Simonov (@simonoff) -* Alan Harper (@aussiegeek) ### Original author From 2b2e0ee56853f9ced9bfee3e8f7eee1ffcae5a68 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 18 May 2021 19:23:56 +0100 Subject: [PATCH 217/469] Bump version to 3.0.0. There are breaking changes in the recent PRs that have been merged. --- lib/zip/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zip/version.rb b/lib/zip/version.rb index 0b20c214..55d4ac29 100644 --- a/lib/zip/version.rb +++ b/lib/zip/version.rb @@ -1,3 +1,3 @@ module Zip - VERSION = '2.3.0' + VERSION = '3.0.0' end From 25795c7b0e6c9918d09ceb5420c1b6f9fdc26780 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 18 May 2021 19:24:47 +0100 Subject: [PATCH 218/469] Update Changelog with some recent merged PRs. --- Changelog.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 5a1bbe12..3a88ab3e 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,10 +1,21 @@ -# X.X.X (Next) +# 3.0.0 (Next) +- Replace and deprecate `Zip::DOSTime#dos_equals`. [#464](https://github.com/rubyzip/rubyzip/pull/464) +- Fix input stream partial read error. [#462](https://github.com/rubyzip/rubyzip/pull/462) +- Fix loading extra fields. [#459](https://github.com/rubyzip/rubyzip/pull/459) - Set compression level on a per-zipfile basis. [#448](https://github.com/rubyzip/rubyzip/pull/448) - Fix input stream partial read error. [#462](https://github.com/rubyzip/rubyzip/pull/462) +- Fix zlib deflate buffer growth. [#447](https://github.com/rubyzip/rubyzip/pull/447) Tooling: +- Add GitHub Actions CI infrastructure. [#469](https://github.com/rubyzip/rubyzip/issues/469) +- Add Ruby 3.0 to CI. [#474](https://github.com/rubyzip/rubyzip/pull/474) +- Fix the compression level tests to compare relative sizes. [#473](https://github.com/rubyzip/rubyzip/pull/473) +- Simplify assertions in basic_zip_file_test. [#470](https://github.com/rubyzip/rubyzip/pull/470) +- Remove compare_enumerables from test_helper.rb. [#468](https://github.com/rubyzip/rubyzip/pull/468) +- Use correct SPDX license identifier. [#458](https://github.com/rubyzip/rubyzip/pull/458) +- Enable truffle ruby in Travis CI. [#450](https://github.com/rubyzip/rubyzip/pull/450) - Update rubocop again and run it in CI. [#444](https://github.com/rubyzip/rubyzip/pull/444) - Fix a test that was incorrect on big-endian architectures. [#445](https://github.com/rubyzip/rubyzip/pull/445) From 34237efc002d66b0a33c09998b2d4e38bb3f7507 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 18 May 2021 19:51:06 +0100 Subject: [PATCH 219/469] Ensure that `Entry#time=` sets times as `DOSTime` objects. Fixes #481. --- Changelog.md | 1 + lib/zip/entry.rb | 2 ++ test/entry_test.rb | 9 +++++++++ 3 files changed, 12 insertions(+) diff --git a/Changelog.md b/Changelog.md index 3a88ab3e..24d6e3e1 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,6 @@ # 3.0.0 (Next) +- Ensure that `Entry#time=` sets times as `DOSTime` objects. [#481](https://github.com/rubyzip/rubyzip/issues/481) - Replace and deprecate `Zip::DOSTime#dos_equals`. [#464](https://github.com/rubyzip/rubyzip/pull/464) - Fix input stream partial read error. [#462](https://github.com/rubyzip/rubyzip/pull/462) - Fix loading extra fields. [#459](https://github.com/rubyzip/rubyzip/pull/459) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index e6875e3e..9f658723 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -121,6 +121,8 @@ def time=(value) unless @extra.member?('UniversalTime') || @extra.member?('NTFS') @extra.create('UniversalTime') end + + value = DOSTime.from_time(value) (@extra['UniversalTime'] || @extra['NTFS']).mtime = value @time = value end diff --git a/test/entry_test.rb b/test/entry_test.rb index dda0a7ba..36390e87 100644 --- a/test/entry_test.rb +++ b/test/entry_test.rb @@ -254,4 +254,13 @@ def test_compression_method_reader ) assert_equal(Zip::Entry::STORED, entry.compression_method) end + + def test_set_time_as_dos_time + entry = ::Zip::Entry.new + assert(entry.time.kind_of?(::Zip::DOSTime)) + entry.time = Time.now + assert(entry.time.kind_of?(::Zip::DOSTime)) + entry.time = ::Zip::DOSTime.now + assert(entry.time.kind_of?(::Zip::DOSTime)) + end end From bae056efb4def85c3cc1cba65bb1a7d603cfb928 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 18 May 2021 21:12:49 +0100 Subject: [PATCH 220/469] Optimise the GitHub Actions tests workflow. --- .github/workflows/tests.yml | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7760ad69..c67d988d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,17 +7,9 @@ jobs: strategy: fail-fast: false matrix: - ruby: [2.4, 2.5, 2.6, 2.7, '3.0', jruby, truffleruby] - can-fail: [false] - include: - - ruby: head - can-fail: true - - ruby: jruby-head - can-fail: true - - ruby: truffleruby-head - can-fail: true + ruby: [2.4, 2.5, 2.6, 2.7, '3.0', head, jruby, jruby-head, truffleruby, truffleruby-head] runs-on: ubuntu-latest - continue-on-error: ${{ matrix.can-fail }} + continue-on-error: ${{ endsWith(matrix.ruby, 'head') }} steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 From df9d39730eeb042b1935a9ea5a0c41b4e718062e Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 18 May 2021 21:21:41 +0100 Subject: [PATCH 221/469] Add macos and windows tests to Actions. Just one run of each for now should be enough. Allow windows tests to fail for now as our tests are broken there at the moment. --- .github/workflows/tests.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c67d988d..fec5899d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,9 +7,15 @@ jobs: strategy: fail-fast: false matrix: + os: [ubuntu] ruby: [2.4, 2.5, 2.6, 2.7, '3.0', head, jruby, jruby-head, truffleruby, truffleruby-head] - runs-on: ubuntu-latest - continue-on-error: ${{ endsWith(matrix.ruby, 'head') }} + include: + - os: macos + ruby: 2.4 + - os: windows + ruby: 2.4 + runs-on: ${{ matrix.os }}-latest + continue-on-error: ${{ endsWith(matrix.ruby, 'head') || matrix.os == 'windows' }} steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 From 8702876e552c7b8b658ee0eadf4a6d4380547a9d Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 18 May 2021 21:55:03 +0100 Subject: [PATCH 222/469] Set the default `Entry` time to the file's mtime on Windows. For some reason this was being skipped on Windows, but not Linux or MacOS. --- Changelog.md | 1 + lib/zip/entry.rb | 6 +++--- test/entry_test.rb | 6 ++++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Changelog.md b/Changelog.md index 24d6e3e1..36c4b7c6 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,6 @@ # 3.0.0 (Next) +- Set the default `Entry` time to the file's mtime on Windows. [#465](https://github.com/rubyzip/rubyzip/issues/465) - Ensure that `Entry#time=` sets times as `DOSTime` objects. [#481](https://github.com/rubyzip/rubyzip/issues/481) - Replace and deprecate `Zip::DOSTime#dos_equals`. [#464](https://github.com/rubyzip/rubyzip/pull/464) - Fix input stream partial read error. [#462](https://github.com/rubyzip/rubyzip/pull/462) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 9f658723..0b13470d 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -442,13 +442,13 @@ def file_stat(path) # :nodoc: end def get_extra_attributes_from_path(path) # :nodoc: - return if Zip::RUNNING_ON_WINDOWS + stat = file_stat(path) + @time = DOSTime.from_time(stat.mtime) + return if ::Zip::RUNNING_ON_WINDOWS - stat = file_stat(path) @unix_uid = stat.uid @unix_gid = stat.gid @unix_perms = stat.mode & 0o7777 - @time = ::Zip::DOSTime.from_time(stat.mtime) end def set_unix_attributes_on_path(dest_path) diff --git a/test/entry_test.rb b/test/entry_test.rb index 36390e87..e6ac7a4b 100644 --- a/test/entry_test.rb +++ b/test/entry_test.rb @@ -263,4 +263,10 @@ def test_set_time_as_dos_time entry.time = ::Zip::DOSTime.now assert(entry.time.kind_of?(::Zip::DOSTime)) end + + def test_ensure_entry_time_set_to_file_mtime + entry = ::Zip::Entry.new + entry.gather_fileinfo_from_srcpath('test/data/mimetype') + assert_equal(entry.time, File.stat('test/data/mimetype').mtime) + end end From 43d99840441c4bb4a4fcc9966c24bc6e3d5d4315 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 18 May 2021 22:43:22 +0100 Subject: [PATCH 223/469] Install zip for the Windows CI test Action. And remove hardcoded paths for zip in the tests. --- .github/workflows/tests.yml | 15 ++++++++++++--- test/gentestfiles.rb | 14 +++++++------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fec5899d..a8794f8c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,9 +17,18 @@ jobs: runs-on: ${{ matrix.os }}-latest continue-on-error: ${{ endsWith(matrix.ruby, 'head') || matrix.os == 'windows' }} steps: - - uses: actions/checkout@v2 - - uses: ruby/setup-ruby@v1 + - name: Checkout rubyzip code + uses: actions/checkout@v2 + + - name: Install and set up ruby + uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true - - run: bundle exec rake + + - name: Install other dependencies + if: matrix.os == 'windows' + run: choco install zip + + - name: Run the tests + run: bundle exec rake diff --git a/test/gentestfiles.rb b/test/gentestfiles.rb index 503a0d00..bb1b981d 100755 --- a/test/gentestfiles.rb +++ b/test/gentestfiles.rb @@ -73,10 +73,10 @@ def initialize(zip_name, entry_names, comment = '') def self.create_test_zips raise "failed to create test zip '#{TEST_ZIP1.zip_name}'" \ - unless system("/usr/bin/zip -q #{TEST_ZIP1.zip_name} test/data/file2.txt") + unless system("zip -q #{TEST_ZIP1.zip_name} test/data/file2.txt") raise "failed to remove entry from '#{TEST_ZIP1.zip_name}'" \ unless system( - "/usr/bin/zip -q #{TEST_ZIP1.zip_name} -d test/data/file2.txt" + "zip -q #{TEST_ZIP1.zip_name} -d test/data/file2.txt" ) File.open('test/data/generated/empty.txt', 'w') {} @@ -102,31 +102,31 @@ def self.create_test_zips raise "failed to create test zip '#{TEST_ZIP2.zip_name}'" \ unless system( - "/usr/bin/zip -q #{TEST_ZIP2.zip_name} #{TEST_ZIP2.entry_names.join(' ')}" + "zip -q #{TEST_ZIP2.zip_name} #{TEST_ZIP2.entry_names.join(' ')}" ) if RUBY_PLATFORM =~ /mswin|mingw|cygwin/ raise "failed to add comment to test zip '#{TEST_ZIP2.zip_name}'" \ unless system( - "echo #{TEST_ZIP2.comment}| /usr/bin/zip -zq #{TEST_ZIP2.zip_name}\"" + "echo #{TEST_ZIP2.comment}| zip -zq #{TEST_ZIP2.zip_name}\"" ) else # without bash system interprets everything after echo as parameters to # echo including | zip -z ... raise "failed to add comment to test zip '#{TEST_ZIP2.zip_name}'" \ unless system( - "bash -c \"echo #{TEST_ZIP2.comment} | /usr/bin/zip -zq #{TEST_ZIP2.zip_name}\"" + "bash -c \"echo #{TEST_ZIP2.comment} | zip -zq #{TEST_ZIP2.zip_name}\"" ) end raise "failed to create test zip '#{TEST_ZIP3.zip_name}'" \ unless system( - "/usr/bin/zip -q #{TEST_ZIP3.zip_name} #{TEST_ZIP3.entry_names.join(' ')}" + "zip -q #{TEST_ZIP3.zip_name} #{TEST_ZIP3.entry_names.join(' ')}" ) raise "failed to create test zip '#{TEST_ZIP4.zip_name}'" \ unless system( - "/usr/bin/zip -q #{TEST_ZIP4.zip_name} #{TEST_ZIP4.entry_names.join(' ')}" + "zip -q #{TEST_ZIP4.zip_name} #{TEST_ZIP4.entry_names.join(' ')}" ) rescue StandardError # If there are any Windows developers wanting to use a command line zip.exe From 3958039497e8bdcb159e6156f7670c78ee268988 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 18 May 2021 23:12:51 +0100 Subject: [PATCH 224/469] Name the steps in the linter Action. To match those of the tests Action. --- .github/workflows/lint.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index fc6b09bd..dccec7f3 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -6,10 +6,14 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: ruby/setup-ruby@v1 + - name: Checkout rubyzip code + uses: actions/checkout@v2 + + - name: Install and set up ruby + uses: ruby/setup-ruby@v1 with: ruby-version: 2.4 bundler-cache: true + - name: Rubocop run: bundle exec rubocop From db3ce93027bdfb6718d127a5bd50224211bf8a4a Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 19 May 2021 21:39:53 +0100 Subject: [PATCH 225/469] Add coveralls integration to the GitHub CI Action. Fixes #480. --- .github/workflows/tests.yml | 18 ++++++++++++++++++ .simplecov | 15 ++++++++++++--- rubyzip.gemspec | 3 ++- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a8794f8c..467bc0f2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -32,3 +32,21 @@ jobs: - name: Run the tests run: bundle exec rake + + - name: Coveralls + if: matrix.os == 'ubuntu' && !endsWith(matrix.ruby, 'head') + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.github_token }} + flag-name: ${{ matrix.ruby }} + parallel: true + + finish: + needs: test + runs-on: ubuntu-latest + steps: + - name: Coveralls Finished + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.github_token }} + parallel-finished: true diff --git a/.simplecov b/.simplecov index 770d7dc6..fc3979d2 100644 --- a/.simplecov +++ b/.simplecov @@ -1,9 +1,18 @@ -require 'coveralls' +require 'simplecov-lcov' + +SimpleCov::Formatter::LcovFormatter.config do |c| + c.output_directory = 'coverage' + c.lcov_file_name = 'lcov.info' + c.report_with_single_file = true + c.single_report_path = 'coverage/lcov.info' +end SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([ SimpleCov::Formatter::HTMLFormatter, - Coveralls::SimpleCov::Formatter + SimpleCov::Formatter::LcovFormatter ]) + SimpleCov.start do - add_filter '/test' + # enable_coverage :branch <-- Re-enable this when we move to ruby ~> 2.5. + add_filter ['/test/', '/samples/'] end diff --git a/rubyzip.gemspec b/rubyzip.gemspec index 67de78dc..da062710 100644 --- a/rubyzip.gemspec +++ b/rubyzip.gemspec @@ -21,9 +21,10 @@ Gem::Specification.new do |s| 'wiki_uri' => 'https://github.com/rubyzip/rubyzip/wiki' } s.required_ruby_version = '>= 2.4' - s.add_development_dependency 'coveralls', '~> 0.7' s.add_development_dependency 'minitest', '~> 5.4' s.add_development_dependency 'pry', '~> 0.10' s.add_development_dependency 'rake', '~> 12.3', '>= 12.3.3' s.add_development_dependency 'rubocop', '~> 0.80.1' + s.add_development_dependency 'simplecov', '~> 0.18.0' + s.add_development_dependency 'simplecov-lcov', '~> 0.8' end From f0b50d3c6c7e54bbfeea7486c3a20eb39706bd86 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 19 May 2021 22:23:54 +0100 Subject: [PATCH 226/469] Add JRUBY_OPTS=--debug to the CI environment. --- .github/workflows/tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 467bc0f2..bc4c0710 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -31,6 +31,8 @@ jobs: run: choco install zip - name: Run the tests + env: + JRUBY_OPTS: --debug run: bundle exec rake - name: Coveralls From af716bef324fa0935aedb80b2124ecbd91beb5c3 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 25 May 2021 18:03:28 +0200 Subject: [PATCH 227/469] Refactor assert_forwarded so it does not need ObjectSpace._id2ref or eval --- test/test_helper.rb | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/test/test_helper.rb b/test/test_helper.rb index d4e52354..a42ecbcf 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -182,22 +182,21 @@ def setup module ExtraAssertions def assert_forwarded(object, method, ret_val, *expected_args) call_args = nil - call_args_proc = proc { |args| call_args = args } - object.instance_eval <<-END_EVAL, __FILE__, __LINE__ + 1 - alias #{method}_org #{method} - def #{method}(*args) - ObjectSpace._id2ref(#{call_args_proc.object_id}).call(args) - ObjectSpace._id2ref(#{ret_val.object_id}) - end - END_EVAL + object.singleton_class.class_exec do + alias_method :"#{method}_org", method + define_method(method) do |*args| + call_args = args + ret_val + end + end assert_equal(ret_val, yield) # Invoke test assert_equal(expected_args, call_args) ensure - object.instance_eval <<-END_EVAL, __FILE__, __LINE__ + 1 - undef #{method} - alias #{method} #{method}_org - END_EVAL + object.singleton_class.class_exec do + remove_method method + alias_method method, :"#{method}_org" + end end end From 3b3b932f2dff55445d607221118ec10adf18dfbe Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 25 May 2021 19:53:42 +0100 Subject: [PATCH 228/469] No longer need to turn on `objectspace` in JRuby. --- test/test_helper.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/test_helper.rb b/test/test_helper.rb index a42ecbcf..35a5085c 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -10,11 +10,6 @@ TestFiles.create_test_files TestZipFile.create_test_zips -if defined? JRUBY_VERSION - require 'jruby' - JRuby.objectspace = true -end - ::MiniTest.after_run do FileUtils.rm_rf('test/data/generated') end From cb69bd520ff2734f2becc47e8fce21c1182ca3e4 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 25 May 2021 19:58:08 +0100 Subject: [PATCH 229/469] Update Changelog. --- Changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changelog.md b/Changelog.md index 36c4b7c6..293acfb3 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,6 @@ # 3.0.0 (Next) +- Fix frozen string literal error. [#475](https://github.com/rubyzip/rubyzip/pull/475) - Set the default `Entry` time to the file's mtime on Windows. [#465](https://github.com/rubyzip/rubyzip/issues/465) - Ensure that `Entry#time=` sets times as `DOSTime` objects. [#481](https://github.com/rubyzip/rubyzip/issues/481) - Replace and deprecate `Zip::DOSTime#dos_equals`. [#464](https://github.com/rubyzip/rubyzip/pull/464) @@ -11,6 +12,7 @@ Tooling: +- Refactor `assert_forwarded` so it does not need `ObjectSpace._id2ref` or `eval`. [#483](https://github.com/rubyzip/rubyzip/pull/483) - Add GitHub Actions CI infrastructure. [#469](https://github.com/rubyzip/rubyzip/issues/469) - Add Ruby 3.0 to CI. [#474](https://github.com/rubyzip/rubyzip/pull/474) - Fix the compression level tests to compare relative sizes. [#473](https://github.com/rubyzip/rubyzip/pull/473) From f1e73b047eb62f4eaed4d3e6a4b18b233babc42c Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 23 May 2021 17:14:30 +0100 Subject: [PATCH 230/469] Tidy up dependencies in gemspec. --- rubyzip.gemspec | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rubyzip.gemspec b/rubyzip.gemspec index da062710..e6d4c5bf 100644 --- a/rubyzip.gemspec +++ b/rubyzip.gemspec @@ -22,8 +22,7 @@ Gem::Specification.new do |s| } s.required_ruby_version = '>= 2.4' s.add_development_dependency 'minitest', '~> 5.4' - s.add_development_dependency 'pry', '~> 0.10' - s.add_development_dependency 'rake', '~> 12.3', '>= 12.3.3' + s.add_development_dependency 'rake', '~> 12.3.3' s.add_development_dependency 'rubocop', '~> 0.80.1' s.add_development_dependency 'simplecov', '~> 0.18.0' s.add_development_dependency 'simplecov-lcov', '~> 0.8' From 3d33e4a8e0f9f70e29ecee4008a75814364fb524 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 23 May 2021 17:44:36 +0100 Subject: [PATCH 231/469] Update Rubocop version. Now using as late a version as we can for Ruby 2.4. --- .rubocop.yml | 3 +- .rubocop_todo.yml | 186 +++++++++++++++++++++++++++++++++++++++------- rubyzip.gemspec | 2 +- 3 files changed, 162 insertions(+), 29 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index ef285c5e..ba47414b 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -4,6 +4,7 @@ inherit_from: .rubocop_todo.yml # we get errors if our ruby version doesn't match. AllCops: TargetRubyVersion: 2.4 + NewCops: enable Layout/HashAlignment: EnforcedHashRocketStyle: table @@ -24,7 +25,7 @@ Lint/SuppressedException: - 'test/**/*.rb' # Allow this "useless" test, as we are testing <=> here. -Lint/UselessComparison: +Lint/BinaryOperatorWithIdenticalOperands: Exclude: - 'test/entry_test.rb' diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 0a045c53..7e98e30b 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,31 +1,84 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2020-03-14 17:50:29 +0000 using RuboCop version 0.80.0. +# on 2021-05-23 16:31:53 UTC using RuboCop version 1.12.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. +# Offense count: 8 +# Cop supports --auto-correct. +# Configuration parameters: EmptyLineBetweenMethodDefs, EmptyLineBetweenClassDefs, EmptyLineBetweenModuleDefs, AllowAdjacentOneLineDefs, NumberOfEmptyLines. +Layout/EmptyLineBetweenDefs: + Exclude: + - 'lib/zip/errors.rb' + +# Offense count: 3 +# Cop supports --auto-correct. +# Configuration parameters: AllowAliasSyntax, AllowedMethods. +# AllowedMethods: alias_method, public, protected, private +Layout/EmptyLinesAroundAttributeAccessor: + Exclude: + - 'lib/zip/extra_field/zip64.rb' + - 'lib/zip/filesystem.rb' + - 'samples/gtk_ruby_zip.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: IndentationWidth. +# SupportedStyles: special_inside_parentheses, consistent, align_brackets +Layout/FirstArrayElementIndentation: + EnforcedStyle: consistent + +# Offense count: 5 +# Configuration parameters: AllowComments, AllowEmptyLambdas. +Lint/EmptyBlock: + Exclude: + - 'lib/zip/file.rb' + - 'test/gentestfiles.rb' + - 'test/settings_test.rb' + +# Offense count: 3 +# Configuration parameters: AllowComments. +Lint/EmptyClass: + Exclude: + - 'lib/zip/crypto/encryption.rb' + - 'test/decompressor_test.rb' + +# Offense count: 7 +Lint/MissingSuper: + Exclude: + - 'lib/zip/extra_field.rb' + - 'lib/zip/extra_field/ntfs.rb' + - 'lib/zip/extra_field/old_unix.rb' + - 'lib/zip/extra_field/universal_time.rb' + - 'lib/zip/extra_field/unix.rb' + - 'lib/zip/extra_field/zip64.rb' + - 'lib/zip/extra_field/zip64_placeholder.rb' + # Offense count: 5 -# Configuration parameters: CountComments. +# Configuration parameters: CountComments, CountAsOne. Metrics/ClassLength: - Max: 610 + Max: 601 -# Offense count: 26 +# Offense count: 20 +# Configuration parameters: IgnoredMethods. Metrics/CyclomaticComplexity: - Max: 15 + Max: 14 -# Offense count: 44 -# Configuration parameters: CountComments, ExcludedMethods. +# Offense count: 46 +# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods. Metrics/MethodLength: Max: 32 -# Offense count: 2 +# Offense count: 5 # Configuration parameters: CountKeywordArgs. Metrics/ParameterLists: - Max: 12 + Max: 11 + MaxOptionalParameters: 9 -# Offense count: 20 +# Offense count: 14 +# Configuration parameters: IgnoredMethods. Metrics/PerceivedComplexity: Max: 15 @@ -37,18 +90,27 @@ Naming/AccessorMethodName: - 'lib/zip/input_stream.rb' - 'lib/zip/streamable_stream.rb' -# Offense count: 7 +# Offense count: 4 +# Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers. +# SupportedStyles: snake_case, normalcase, non_integer +# AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339 +Naming/VariableNumber: + Exclude: + - 'test/file_extract_test.rb' + - 'test/file_permissions_test.rb' + +# Offense count: 4 +# Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. -# SupportedStyles: inline, group -Style/AccessModifierDeclarations: +# SupportedStyles: separated, grouped +Style/AccessorGrouping: Exclude: - - 'lib/zip/central_directory.rb' - - 'lib/zip/extra_field/zip64.rb' - - 'lib/zip/filesystem.rb' + - 'lib/zip/decompressor.rb' + - 'lib/zip/entry.rb' # Offense count: 7 # Cop supports --auto-correct. -# Configuration parameters: AutoCorrect, EnforcedStyle. +# Configuration parameters: EnforcedStyle. # SupportedStyles: nested, compact Style/ClassAndModuleChildren: Exclude: @@ -60,26 +122,50 @@ Style/ClassAndModuleChildren: - 'lib/zip/extra_field/zip64.rb' - 'lib/zip/extra_field/zip64_placeholder.rb' -# Offense count: 25 +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: Keywords. +# Keywords: TODO, FIXME, OPTIMIZE, HACK, REVIEW, NOTE +Style/CommentAnnotation: + Exclude: + - 'test/file_test.rb' + - 'test/zip64_full_test.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +Style/Dir: + Exclude: + - 'test/file_test.rb' + +# Offense count: 2 +Style/DocumentDynamicEvalDefinition: + Exclude: + - 'test/test_helper.rb' + +# Offense count: 24 +# Configuration parameters: AllowedConstants. Style/Documentation: Enabled: false -# Offense count: 3 -# Configuration parameters: . +# Offense count: 2 +# Configuration parameters: MaxUnannotatedPlaceholdersAllowed, IgnoredMethods. # SupportedStyles: annotated, template, unannotated Style/FormatStringToken: EnforcedStyle: unannotated -# Offense count: 96 +# Offense count: 97 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: always, always_true, never Style/FrozenStringLiteralComment: Enabled: false -# This one has to be off until our base ruby is at least 2.5. -Style/HashTransformKeys: - Enabled: false +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: AllowSplatArgument. +Style/HashConversion: + Exclude: + - 'lib/zip/entry_set.rb' # Offense count: 13 # Cop supports --auto-correct. @@ -94,7 +180,7 @@ Style/IfUnlessModifier: # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, Autocorrect. -# SupportedStyles: module_function, extend_self +# SupportedStyles: module_function, extend_self, forbidden Style/ModuleFunction: Exclude: - 'lib/zip.rb' @@ -106,9 +192,15 @@ Style/ModuleFunction: Style/MutableConstant: Enabled: false -# Offense count: 23 +# Offense count: 2 # Cop supports --auto-correct. -# Configuration parameters: AutoCorrect, EnforcedStyle, IgnoredMethods. +Style/NegatedIfElseCondition: + Exclude: + - 'samples/qtzip.rb' + +# Offense count: 24 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, IgnoredMethods. # SupportedStyles: predicate, comparison Style/NumericPredicate: Exclude: @@ -125,6 +217,35 @@ Style/NumericPredicate: - 'test/file_split_test.rb' - 'test/test_helper.rb' +# Offense count: 6 +# Configuration parameters: AllowedMethods. +# AllowedMethods: respond_to_missing? +Style/OptionalBooleanParameter: + Exclude: + - 'lib/zip/entry.rb' + - 'lib/zip/file.rb' + - 'lib/zip/output_stream.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +Style/RedundantBegin: + Exclude: + - 'lib/zip/dos_time.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +Style/RedundantFileExtensionInRequire: + Exclude: + - 'samples/qtzip.rb' + +# Offense count: 29 +# Cop supports --auto-correct. +Style/RedundantRegexpEscape: + Exclude: + - 'Guardfile' + - 'test/file_extract_test.rb' + - 'test/path_traversal_test.rb' + # Offense count: 17 # Cop supports --auto-correct. # Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods. @@ -138,3 +259,14 @@ Style/SafeNavigation: - 'test/filesystem/file_nonmutating_test.rb' - 'test/filesystem/file_stat_test.rb' - 'test/test_helper.rb' + +# Offense count: 9 +# Cop supports --auto-correct. +Style/StringConcatenation: + Exclude: + - 'lib/zip/filesystem.rb' + - 'samples/gtk_ruby_zip.rb' + - 'test/encryption_test.rb' + - 'test/file_test.rb' + - 'test/ioextras/abstract_input_stream_test.rb' + - 'test/test_helper.rb' diff --git a/rubyzip.gemspec b/rubyzip.gemspec index e6d4c5bf..f858ee80 100644 --- a/rubyzip.gemspec +++ b/rubyzip.gemspec @@ -23,7 +23,7 @@ Gem::Specification.new do |s| s.required_ruby_version = '>= 2.4' s.add_development_dependency 'minitest', '~> 5.4' s.add_development_dependency 'rake', '~> 12.3.3' - s.add_development_dependency 'rubocop', '~> 0.80.1' + s.add_development_dependency 'rubocop', '~> 1.12.0' s.add_development_dependency 'simplecov', '~> 0.18.0' s.add_development_dependency 'simplecov-lcov', '~> 0.8' end From 6f929b603f50c6125cd71bdb33cd527403bbda38 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 23 May 2021 17:58:18 +0100 Subject: [PATCH 232/469] Configure Layout/EmptyLineBetweenDefs cop. --- .rubocop.yml | 5 +++++ .rubocop_todo.yml | 7 ------- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index ba47414b..a51a3608 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -6,6 +6,11 @@ AllCops: TargetRubyVersion: 2.4 NewCops: enable +# Allow this in this file because adding the extra lines is pointless. +Layout/EmptyLineBetweenDefs: + Exclude: + - 'lib/zip/errors.rb' + Layout/HashAlignment: EnforcedHashRocketStyle: table EnforcedColonStyle: table diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 7e98e30b..99324285 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,13 +6,6 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 8 -# Cop supports --auto-correct. -# Configuration parameters: EmptyLineBetweenMethodDefs, EmptyLineBetweenClassDefs, EmptyLineBetweenModuleDefs, AllowAdjacentOneLineDefs, NumberOfEmptyLines. -Layout/EmptyLineBetweenDefs: - Exclude: - - 'lib/zip/errors.rb' - # Offense count: 3 # Cop supports --auto-correct. # Configuration parameters: AllowAliasSyntax, AllowedMethods. From 0e4dc676a0c447537d233b5738da7ecddc26e12e Mon Sep 17 00:00:00 2001 From: Taichi Ishitani Date: Sat, 6 Mar 2021 09:36:09 +0900 Subject: [PATCH 233/469] fix frozen string literal error --- lib/zip/extra_field/ntfs.rb | 2 +- lib/zip/extra_field/zip64.rb | 2 +- lib/zip/file.rb | 2 +- lib/zip/filesystem.rb | 4 ++-- lib/zip/inflater.rb | 2 +- lib/zip/ioextras.rb | 4 ++-- lib/zip/ioextras/abstract_input_stream.rb | 6 +++--- lib/zip/pass_thru_decompressor.rb | 2 +- test/encryption_test.rb | 2 +- test/extra_field_test.rb | 2 +- test/file_test.rb | 2 +- test/input_stream_test.rb | 4 ++-- test/ioextras/abstract_output_stream_test.rb | 14 +++++++------- test/output_stream_test.rb | 4 ++-- test/test_helper.rb | 2 +- 15 files changed, 27 insertions(+), 27 deletions(-) diff --git a/lib/zip/extra_field/ntfs.rb b/lib/zip/extra_field/ntfs.rb index f4f11b2d..f80b5078 100644 --- a/lib/zip/extra_field/ntfs.rb +++ b/lib/zip/extra_field/ntfs.rb @@ -51,7 +51,7 @@ def pack_for_c_dir # reserved 0 and tag 1 s = [0, 1].pack('Vv') - tag1 = ''.force_encoding(Encoding::BINARY) + tag1 = (+'').force_encoding(Encoding::BINARY) if @mtime tag1 << [to_ntfs_time(@mtime)].pack('Q<') if @atime diff --git a/lib/zip/extra_field/zip64.rb b/lib/zip/extra_field/zip64.rb index 9826c6cf..875fc625 100644 --- a/lib/zip/extra_field/zip64.rb +++ b/lib/zip/extra_field/zip64.rb @@ -59,7 +59,7 @@ def pack_for_local def pack_for_c_dir # central directory entries contain only fields that didn't fit in the main entry part - packed = ''.force_encoding('BINARY') + packed = (+'').force_encoding('BINARY') packed << [@original_size].pack('Q<') if @original_size packed << [@compressed_size].pack('Q<') if @compressed_size packed << [@relative_header_offset].pack('Q<') if @relative_header_offset diff --git a/lib/zip/file.rb b/lib/zip/file.rb index c96d6615..65784565 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -133,7 +133,7 @@ def open(file_name, create = false, options = {}) # Same as #open. But outputs data to a buffer instead of a file def add_buffer - io = ::StringIO.new('') + io = ::StringIO.new(+'') zf = ::Zip::File.new(io, true, true) yield zf zf.write_buffer(io) diff --git a/lib/zip/filesystem.rb b/lib/zip/filesystem.rb index d9928d4a..6981b071 100644 --- a/lib/zip/filesystem.rb +++ b/lib/zip/filesystem.rb @@ -239,7 +239,7 @@ def directory?(filename) end def open(filename, mode = 'r', permissions = 0o644, &block) - mode.delete!('b') # ignore b option + mode = mode.tr('b', '') # ignore b option case mode when 'r' @mapped_zip.get_input_stream(filename, &block) @@ -619,7 +619,7 @@ def each end def expand_path(path) - expanded = path.start_with?('/') ? path : ::File.join(@pwd, path) + expanded = ::File.expand_path(path, @pwd) expanded.gsub!(/\/\.(\/|$)/, '') expanded.gsub!(/[^\/]+\/\.\.(\/|$)/, '') expanded.empty? ? '/' : expanded diff --git a/lib/zip/inflater.rb b/lib/zip/inflater.rb index 530f98aa..705ab0b5 100644 --- a/lib/zip/inflater.rb +++ b/lib/zip/inflater.rb @@ -7,7 +7,7 @@ def initialize(*args) @zlib_inflater = ::Zlib::Inflate.new(-Zlib::MAX_WBITS) end - def read(length = nil, outbuf = '') + def read(length = nil, outbuf = +'') return (length.nil? || length.zero? ? '' : nil) if eof while length.nil? || (@buffer.bytesize < length) diff --git a/lib/zip/ioextras.rb b/lib/zip/ioextras.rb index 63774d33..7a97a518 100644 --- a/lib/zip/ioextras.rb +++ b/lib/zip/ioextras.rb @@ -6,14 +6,14 @@ module IOExtras #:nodoc: class << self def copy_stream(ostream, istream) - ostream.write(istream.read(CHUNK_SIZE, '')) until istream.eof? + ostream.write(istream.read(CHUNK_SIZE, +'')) until istream.eof? end def copy_stream_n(ostream, istream, nbytes) toread = nbytes while toread > 0 && !istream.eof? tr = toread > CHUNK_SIZE ? CHUNK_SIZE : toread - ostream.write(istream.read(tr, '')) + ostream.write(istream.read(tr, +'')) toread -= tr end end diff --git a/lib/zip/ioextras/abstract_input_stream.rb b/lib/zip/ioextras/abstract_input_stream.rb index 848dcae3..bf32811a 100644 --- a/lib/zip/ioextras/abstract_input_stream.rb +++ b/lib/zip/ioextras/abstract_input_stream.rb @@ -11,13 +11,13 @@ def initialize super @lineno = 0 @pos = 0 - @output_buffer = '' + @output_buffer = +'' end attr_accessor :lineno attr_reader :pos - def read(number_of_bytes = nil, buf = '') + def read(number_of_bytes = nil, buf = +'') tbuf = if @output_buffer.bytesize > 0 if number_of_bytes && number_of_bytes <= @output_buffer.bytesize @output_buffer.slice!(0, number_of_bytes) @@ -93,7 +93,7 @@ def ungetc(byte) def flush ret_val = @output_buffer - @output_buffer = '' + @output_buffer = +'' ret_val end diff --git a/lib/zip/pass_thru_decompressor.rb b/lib/zip/pass_thru_decompressor.rb index e638540e..22db40e0 100644 --- a/lib/zip/pass_thru_decompressor.rb +++ b/lib/zip/pass_thru_decompressor.rb @@ -5,7 +5,7 @@ def initialize(*args) @read_so_far = 0 end - def read(length = nil, outbuf = '') + def read(length = nil, outbuf = +'') return (length.nil? || length.zero? ? '' : nil) if eof if length.nil? || (@read_so_far + length) > decompressed_size diff --git a/test/encryption_test.rb b/test/encryption_test.rb index cad0525a..0b59e64e 100644 --- a/test/encryption_test.rb +++ b/test/encryption_test.rb @@ -18,7 +18,7 @@ def test_encrypt password = 'swordfish' - encrypted_zip = Zip::OutputStream.write_buffer(::StringIO.new(''), Zip::TraditionalEncrypter.new(password)) do |out| + encrypted_zip = Zip::OutputStream.write_buffer(::StringIO.new(+''), Zip::TraditionalEncrypter.new(password)) do |out| out.put_next_entry(test_filename) out.write content end diff --git a/test/extra_field_test.rb b/test/extra_field_test.rb index 52140a2d..c8958fad 100644 --- a/test/extra_field_test.rb +++ b/test/extra_field_test.rb @@ -28,7 +28,7 @@ def test_bad_header_id end def test_ntfs - str = "\x0A\x00 \x00\x00\x00\x00\x00\x01\x00\x18\x00\xC0\x81\x17\xE8B\xCE\xCF\x01\xC0\x81\x17\xE8B\xCE\xCF\x01\xC0\x81\x17\xE8B\xCE\xCF\x01" + str = +"\x0A\x00 \x00\x00\x00\x00\x00\x01\x00\x18\x00\xC0\x81\x17\xE8B\xCE\xCF\x01\xC0\x81\x17\xE8B\xCE\xCF\x01\xC0\x81\x17\xE8B\xCE\xCF\x01" extra = ::Zip::ExtraField.new(str) assert(extra.member?('NTFS')) t = ::Zip::DOSTime.at(1_410_496_497.405178) diff --git a/test/file_test.rb b/test/file_test.rb index f283a884..bdba5696 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -536,7 +536,7 @@ def test_write_buffer zf = ::Zip::File.new(TEST_ZIP.zip_name) old_name = zf.entries.first zf.rename(old_name, new_name) - io = ::StringIO.new('') + io = ::StringIO.new(+'') buffer = zf.write_buffer(io) File.open(TEST_ZIP.zip_name, 'wb') { |f| f.write buffer.string } zf_read = ::Zip::File.new(TEST_ZIP.zip_name) diff --git a/test/input_stream_test.rb b/test/input_stream_test.rb index 34042ba9..a1adf10b 100644 --- a/test/input_stream_test.rb +++ b/test/input_stream_test.rb @@ -134,7 +134,7 @@ def test_rewind assert_equal(TestZipFile::TEST_ZIP2.entry_names[0], e.name) # Do a little reading - buf = '' + buf = +'' buf << zis.read(100) assert_equal(100, zis.pos) buf << (zis.gets || '') @@ -143,7 +143,7 @@ def test_rewind zis.rewind - buf2 = '' + buf2 = +'' buf2 << zis.read(100) buf2 << (zis.gets || '') buf2 << (zis.gets || '') diff --git a/test/ioextras/abstract_output_stream_test.rb b/test/ioextras/abstract_output_stream_test.rb index 9b02309c..8c612445 100644 --- a/test/ioextras/abstract_output_stream_test.rb +++ b/test/ioextras/abstract_output_stream_test.rb @@ -8,7 +8,7 @@ class TestOutputStream attr_accessor :buffer def initialize - @buffer = '' + @buffer = +'' end def <<(data) @@ -58,12 +58,12 @@ def test_print assert_equal("hello world. You ok out there?\nI sure hope so!\n", @output_stream.buffer) $, = 'X' - @output_stream.buffer = '' + @output_stream.buffer = +'' @output_stream.print('monkey', 'duck', 'zebra') assert_equal("monkeyXduckXzebra\n", @output_stream.buffer) $\ = nil - @output_stream.buffer = '' + @output_stream.buffer = +'' @output_stream.print(20) assert_equal('20', @output_stream.buffer) end @@ -87,19 +87,19 @@ def test_puts @output_stream.puts('hello', 'world') assert_equal("\nhello\nworld\n", @output_stream.buffer) - @output_stream.buffer = '' + @output_stream.buffer = +'' @output_stream.puts("hello\n", "world\n") assert_equal("hello\nworld\n", @output_stream.buffer) - @output_stream.buffer = '' + @output_stream.buffer = +'' @output_stream.puts(%W[hello\n world\n]) assert_equal("hello\nworld\n", @output_stream.buffer) - @output_stream.buffer = '' + @output_stream.buffer = +'' @output_stream.puts(%W[hello\n world\n], 'bingo') assert_equal("hello\nworld\nbingo\n", @output_stream.buffer) - @output_stream.buffer = '' + @output_stream.buffer = +'' @output_stream.puts(16, 20, 50, 'hello') assert_equal("16\n20\n50\nhello\n", @output_stream.buffer) end diff --git a/test/output_stream_test.rb b/test/output_stream_test.rb index 73846742..c8139456 100644 --- a/test/output_stream_test.rb +++ b/test/output_stream_test.rb @@ -23,7 +23,7 @@ def test_open end def test_write_buffer - io = ::StringIO.new('') + io = ::StringIO.new(+'') buffer = ::Zip::OutputStream.write_buffer(io) do |zos| zos.comment = TEST_ZIP.comment write_test_zip(zos) @@ -33,7 +33,7 @@ def test_write_buffer end def test_write_buffer_binmode - io = ::StringIO.new('') + io = ::StringIO.new(+'') buffer = ::Zip::OutputStream.write_buffer(io) do |zos| zos.comment = TEST_ZIP.comment write_test_zip(zos) diff --git a/test/test_helper.rb b/test/test_helper.rb index 35a5085c..212a24c0 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -141,7 +141,7 @@ class TestOutputStream attr_accessor :buffer def initialize - @buffer = '' + @buffer = +'' end def <<(data) From e10badf68e476884d9805125050420d143e1be30 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 23 May 2021 18:24:22 +0100 Subject: [PATCH 234/469] Fix Style/FrozenStringLiteralComment cop. --- .rubocop_todo.yml | 7 ---- .simplecov | 2 + Gemfile | 2 + Guardfile | 2 + Rakefile | 2 + bin/console | 1 + lib/zip.rb | 2 + lib/zip/central_directory.rb | 2 + lib/zip/compressor.rb | 2 + lib/zip/constants.rb | 44 ++++++++++---------- lib/zip/crypto/decrypted_io.rb | 2 + lib/zip/crypto/encryption.rb | 2 + lib/zip/crypto/null_encryption.rb | 2 + lib/zip/crypto/traditional_encryption.rb | 2 + lib/zip/decompressor.rb | 2 + lib/zip/deflater.rb | 2 + lib/zip/dos_time.rb | 2 + lib/zip/entry.rb | 2 + lib/zip/entry_set.rb | 2 + lib/zip/errors.rb | 2 + lib/zip/extra_field.rb | 2 + lib/zip/extra_field/generic.rb | 2 + lib/zip/extra_field/ntfs.rb | 2 + lib/zip/extra_field/old_unix.rb | 2 + lib/zip/extra_field/universal_time.rb | 2 + lib/zip/extra_field/unix.rb | 2 + lib/zip/extra_field/zip64.rb | 2 + lib/zip/extra_field/zip64_placeholder.rb | 2 + lib/zip/file.rb | 2 + lib/zip/filesystem.rb | 2 + lib/zip/inflater.rb | 2 + lib/zip/input_stream.rb | 2 + lib/zip/ioextras.rb | 2 + lib/zip/ioextras/abstract_input_stream.rb | 2 + lib/zip/ioextras/abstract_output_stream.rb | 2 + lib/zip/null_compressor.rb | 2 + lib/zip/null_decompressor.rb | 2 + lib/zip/null_input_stream.rb | 2 + lib/zip/output_stream.rb | 2 + lib/zip/pass_thru_compressor.rb | 2 + lib/zip/pass_thru_decompressor.rb | 2 + lib/zip/streamable_directory.rb | 2 + lib/zip/streamable_stream.rb | 2 + lib/zip/version.rb | 2 + rubyzip.gemspec | 2 + samples/example.rb | 1 + samples/example_filesystem.rb | 1 + samples/example_recursive.rb | 2 + samples/gtk_ruby_zip.rb | 1 + samples/qtzip.rb | 1 + samples/write_simple.rb | 1 + samples/zipfind.rb | 1 + test/basic_zip_file_test.rb | 2 + test/bzip2_support_test.rb | 2 + test/case_sensitivity_test.rb | 2 + test/central_directory_entry_test.rb | 2 + test/central_directory_test.rb | 2 + test/constants_test.rb | 2 + test/crypto/null_encryption_test.rb | 2 + test/crypto/traditional_encryption_test.rb | 2 + test/data/notzippedruby.rb | 1 + test/decompressor_test.rb | 2 + test/deflater_test.rb | 2 + test/encryption_test.rb | 2 + test/entry_set_test.rb | 2 + test/entry_test.rb | 2 + test/errors_test.rb | 2 + test/extra_field_test.rb | 2 + test/extra_field_ut_test.rb | 2 + test/file_extract_directory_test.rb | 2 + test/file_extract_test.rb | 2 + test/file_options_test.rb | 8 ++-- test/file_permissions_test.rb | 2 + test/file_split_test.rb | 2 + test/file_test.rb | 2 + test/filesystem/dir_iterator_test.rb | 2 + test/filesystem/directory_test.rb | 2 + test/filesystem/file_mutating_test.rb | 2 + test/filesystem/file_nonmutating_test.rb | 2 + test/filesystem/file_stat_test.rb | 2 + test/gentestfiles.rb | 1 + test/inflater_test.rb | 2 + test/input_stream_test.rb | 2 + test/ioextras/abstract_input_stream_test.rb | 2 + test/ioextras/abstract_output_stream_test.rb | 2 + test/ioextras/fake_io_test.rb | 2 + test/local_entry_test.rb | 2 + test/output_stream_test.rb | 2 + test/pass_thru_compressor_test.rb | 2 + test/pass_thru_decompressor_test.rb | 2 + test/path_traversal_test.rb | 2 + test/samples/example_recursive_test.rb | 2 + test/settings_test.rb | 2 + test/stored_support_test.rb | 2 + test/test_helper.rb | 2 + test/unicode_file_names_and_comments_test.rb | 2 + test/zip64_full_test.rb | 2 + test/zip64_support_test.rb | 2 + 98 files changed, 209 insertions(+), 31 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 99324285..b120cadf 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -146,13 +146,6 @@ Style/Documentation: Style/FormatStringToken: EnforcedStyle: unannotated -# Offense count: 97 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: always, always_true, never -Style/FrozenStringLiteralComment: - Enabled: false - # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: AllowSplatArgument. diff --git a/.simplecov b/.simplecov index fc3979d2..6c400bf6 100644 --- a/.simplecov +++ b/.simplecov @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'simplecov-lcov' SimpleCov::Formatter::LcovFormatter.config do |c| diff --git a/Gemfile b/Gemfile index fa75df15..7f4f5e95 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + source 'https://rubygems.org' gemspec diff --git a/Guardfile b/Guardfile index 1508e4c9..5365a7a5 100644 --- a/Guardfile +++ b/Guardfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + guard :minitest do # with Minitest::Unit watch(%r{^test/(.*)\/?(.*)_test\.rb$}) diff --git a/Rakefile b/Rakefile index 717c6b73..1fc36185 100644 --- a/Rakefile +++ b/Rakefile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'bundler/gem_tasks' require 'rake/testtask' require 'rubocop/rake_task' diff --git a/bin/console b/bin/console index 6df9a590..15e7b977 100755 --- a/bin/console +++ b/bin/console @@ -1,4 +1,5 @@ #!/usr/bin/env ruby +# frozen_string_literal: true require 'bundler/setup' require 'zip' diff --git a/lib/zip.rb b/lib/zip.rb index 8cf982a5..5e142938 100644 --- a/lib/zip.rb +++ b/lib/zip.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'English' require 'delegate' require 'singleton' diff --git a/lib/zip/central_directory.rb b/lib/zip/central_directory.rb index 5b64c7f5..36a150ba 100644 --- a/lib/zip/central_directory.rb +++ b/lib/zip/central_directory.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip class CentralDirectory include Enumerable diff --git a/lib/zip/compressor.rb b/lib/zip/compressor.rb index 079c1cb0..8c0680e0 100644 --- a/lib/zip/compressor.rb +++ b/lib/zip/compressor.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip class Compressor #:nodoc:all def finish; end diff --git a/lib/zip/constants.rb b/lib/zip/constants.rb index fe89847a..c15aeae2 100644 --- a/lib/zip/constants.rb +++ b/lib/zip/constants.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip RUNNING_ON_WINDOWS = RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/i @@ -38,27 +40,27 @@ module Zip FSTYPE_ATHEOS = 30 FSTYPES = { - FSTYPE_FAT => 'FAT'.freeze, - FSTYPE_AMIGA => 'Amiga'.freeze, - FSTYPE_VMS => 'VMS (Vax or Alpha AXP)'.freeze, - FSTYPE_UNIX => 'Unix'.freeze, - FSTYPE_VM_CMS => 'VM/CMS'.freeze, - FSTYPE_ATARI => 'Atari ST'.freeze, - FSTYPE_HPFS => 'OS/2 or NT HPFS'.freeze, - FSTYPE_MAC => 'Macintosh'.freeze, - FSTYPE_Z_SYSTEM => 'Z-System'.freeze, - FSTYPE_CPM => 'CP/M'.freeze, - FSTYPE_TOPS20 => 'TOPS-20'.freeze, - FSTYPE_NTFS => 'NTFS'.freeze, - FSTYPE_QDOS => 'SMS/QDOS'.freeze, - FSTYPE_ACORN => 'Acorn RISC OS'.freeze, - FSTYPE_VFAT => 'Win32 VFAT'.freeze, - FSTYPE_MVS => 'MVS'.freeze, - FSTYPE_BEOS => 'BeOS'.freeze, - FSTYPE_TANDEM => 'Tandem NSK'.freeze, - FSTYPE_THEOS => 'Theos'.freeze, - FSTYPE_MAC_OSX => 'Mac OS/X (Darwin)'.freeze, - FSTYPE_ATHEOS => 'AtheOS'.freeze + FSTYPE_FAT => 'FAT', + FSTYPE_AMIGA => 'Amiga', + FSTYPE_VMS => 'VMS (Vax or Alpha AXP)', + FSTYPE_UNIX => 'Unix', + FSTYPE_VM_CMS => 'VM/CMS', + FSTYPE_ATARI => 'Atari ST', + FSTYPE_HPFS => 'OS/2 or NT HPFS', + FSTYPE_MAC => 'Macintosh', + FSTYPE_Z_SYSTEM => 'Z-System', + FSTYPE_CPM => 'CP/M', + FSTYPE_TOPS20 => 'TOPS-20', + FSTYPE_NTFS => 'NTFS', + FSTYPE_QDOS => 'SMS/QDOS', + FSTYPE_ACORN => 'Acorn RISC OS', + FSTYPE_VFAT => 'Win32 VFAT', + FSTYPE_MVS => 'MVS', + FSTYPE_BEOS => 'BeOS', + FSTYPE_TANDEM => 'Tandem NSK', + FSTYPE_THEOS => 'Theos', + FSTYPE_MAC_OSX => 'Mac OS/X (Darwin)', + FSTYPE_ATHEOS => 'AtheOS' }.freeze COMPRESSION_METHOD_STORE = 0 diff --git a/lib/zip/crypto/decrypted_io.rb b/lib/zip/crypto/decrypted_io.rb index 61a377da..92ccde63 100644 --- a/lib/zip/crypto/decrypted_io.rb +++ b/lib/zip/crypto/decrypted_io.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip class DecryptedIo #:nodoc:all CHUNK_SIZE = 32_768 diff --git a/lib/zip/crypto/encryption.rb b/lib/zip/crypto/encryption.rb index 4351be1c..c792e38b 100644 --- a/lib/zip/crypto/encryption.rb +++ b/lib/zip/crypto/encryption.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip class Encrypter #:nodoc:all end diff --git a/lib/zip/crypto/null_encryption.rb b/lib/zip/crypto/null_encryption.rb index a93f707c..ae33c40c 100644 --- a/lib/zip/crypto/null_encryption.rb +++ b/lib/zip/crypto/null_encryption.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip module NullEncryption def header_bytesize diff --git a/lib/zip/crypto/traditional_encryption.rb b/lib/zip/crypto/traditional_encryption.rb index 270e9efd..eef3b18d 100644 --- a/lib/zip/crypto/traditional_encryption.rb +++ b/lib/zip/crypto/traditional_encryption.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip module TraditionalEncryption def initialize(password) diff --git a/lib/zip/decompressor.rb b/lib/zip/decompressor.rb index 2f89545c..0d29e618 100644 --- a/lib/zip/decompressor.rb +++ b/lib/zip/decompressor.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip class Decompressor #:nodoc:all CHUNK_SIZE = 32_768 diff --git a/lib/zip/deflater.rb b/lib/zip/deflater.rb index 9f5371c8..8f4827b4 100644 --- a/lib/zip/deflater.rb +++ b/lib/zip/deflater.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip class Deflater < Compressor #:nodoc:all def initialize(output_stream, level = Zip.default_compression, encrypter = NullEncrypter.new) diff --git a/lib/zip/dos_time.rb b/lib/zip/dos_time.rb index 51139dde..264ead28 100644 --- a/lib/zip/dos_time.rb +++ b/lib/zip/dos_time.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rubygems' module Zip diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 0b13470d..453c174b 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'pathname' module Zip class Entry diff --git a/lib/zip/entry_set.rb b/lib/zip/entry_set.rb index 9c503781..a5822453 100644 --- a/lib/zip/entry_set.rb +++ b/lib/zip/entry_set.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip class EntrySet #:nodoc:all include Enumerable diff --git a/lib/zip/errors.rb b/lib/zip/errors.rb index 0ff0e1e1..947f8dc4 100644 --- a/lib/zip/errors.rb +++ b/lib/zip/errors.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip class Error < StandardError; end class EntryExistsError < Error; end diff --git a/lib/zip/extra_field.rb b/lib/zip/extra_field.rb index aa3ef8a8..24352aae 100644 --- a/lib/zip/extra_field.rb +++ b/lib/zip/extra_field.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip class ExtraField < Hash ID_MAP = {} diff --git a/lib/zip/extra_field/generic.rb b/lib/zip/extra_field/generic.rb index 9237a1db..b3db791a 100644 --- a/lib/zip/extra_field/generic.rb +++ b/lib/zip/extra_field/generic.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip class ExtraField::Generic def self.register_map diff --git a/lib/zip/extra_field/ntfs.rb b/lib/zip/extra_field/ntfs.rb index f80b5078..cd691b1d 100644 --- a/lib/zip/extra_field/ntfs.rb +++ b/lib/zip/extra_field/ntfs.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip # PKWARE NTFS Extra Field (0x000a) # Only Tag 0x0001 is supported diff --git a/lib/zip/extra_field/old_unix.rb b/lib/zip/extra_field/old_unix.rb index dfd2ba56..351339fe 100644 --- a/lib/zip/extra_field/old_unix.rb +++ b/lib/zip/extra_field/old_unix.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip # Olf Info-ZIP Extra for UNIX uid/gid and file timestampes class ExtraField::OldUnix < ExtraField::Generic diff --git a/lib/zip/extra_field/universal_time.rb b/lib/zip/extra_field/universal_time.rb index 424c281d..63ef070e 100644 --- a/lib/zip/extra_field/universal_time.rb +++ b/lib/zip/extra_field/universal_time.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip # Info-ZIP Additional timestamp field class ExtraField::UniversalTime < ExtraField::Generic diff --git a/lib/zip/extra_field/unix.rb b/lib/zip/extra_field/unix.rb index d83087e4..f88f1355 100644 --- a/lib/zip/extra_field/unix.rb +++ b/lib/zip/extra_field/unix.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip # Info-ZIP Extra for UNIX uid/gid class ExtraField::IUnix < ExtraField::Generic diff --git a/lib/zip/extra_field/zip64.rb b/lib/zip/extra_field/zip64.rb index 875fc625..2f7c32c9 100644 --- a/lib/zip/extra_field/zip64.rb +++ b/lib/zip/extra_field/zip64.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip # Info-ZIP Extra for Zip64 size class ExtraField::Zip64 < ExtraField::Generic diff --git a/lib/zip/extra_field/zip64_placeholder.rb b/lib/zip/extra_field/zip64_placeholder.rb index dfaa56e8..2619a68e 100644 --- a/lib/zip/extra_field/zip64_placeholder.rb +++ b/lib/zip/extra_field/zip64_placeholder.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip # placeholder to reserve space for a Zip64 extra information record, for the # local file header only, that we won't know if we'll need until after diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 65784565..1c579539 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip # ZipFile is modeled after java.util.zip.ZipFile from the Java SDK. # The most important methods are those inherited from diff --git a/lib/zip/filesystem.rb b/lib/zip/filesystem.rb index 6981b071..04162ca0 100644 --- a/lib/zip/filesystem.rb +++ b/lib/zip/filesystem.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'zip' module Zip diff --git a/lib/zip/inflater.rb b/lib/zip/inflater.rb index 705ab0b5..8f686f3e 100644 --- a/lib/zip/inflater.rb +++ b/lib/zip/inflater.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip class Inflater < Decompressor #:nodoc:all def initialize(*args) diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index f942d190..d044a294 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip # InputStream is the basic class for reading zip entries in a # zip file. It is possible to create a InputStream object directly, diff --git a/lib/zip/ioextras.rb b/lib/zip/ioextras.rb index 7a97a518..879a5145 100644 --- a/lib/zip/ioextras.rb +++ b/lib/zip/ioextras.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip module IOExtras #:nodoc: CHUNK_SIZE = 131_072 diff --git a/lib/zip/ioextras/abstract_input_stream.rb b/lib/zip/ioextras/abstract_input_stream.rb index bf32811a..11438fc7 100644 --- a/lib/zip/ioextras/abstract_input_stream.rb +++ b/lib/zip/ioextras/abstract_input_stream.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip module IOExtras # Implements many of the convenience methods of IO diff --git a/lib/zip/ioextras/abstract_output_stream.rb b/lib/zip/ioextras/abstract_output_stream.rb index b94c9d49..e826b54a 100644 --- a/lib/zip/ioextras/abstract_output_stream.rb +++ b/lib/zip/ioextras/abstract_output_stream.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip module IOExtras # Implements many of the output convenience methods of IO. diff --git a/lib/zip/null_compressor.rb b/lib/zip/null_compressor.rb index 70fd3294..41da0c61 100644 --- a/lib/zip/null_compressor.rb +++ b/lib/zip/null_compressor.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip class NullCompressor < Compressor #:nodoc:all include Singleton diff --git a/lib/zip/null_decompressor.rb b/lib/zip/null_decompressor.rb index 6534b161..7b2557d4 100644 --- a/lib/zip/null_decompressor.rb +++ b/lib/zip/null_decompressor.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip module NullDecompressor #:nodoc:all module_function diff --git a/lib/zip/null_input_stream.rb b/lib/zip/null_input_stream.rb index 2cd36616..69bc38d7 100644 --- a/lib/zip/null_input_stream.rb +++ b/lib/zip/null_input_stream.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip module NullInputStream #:nodoc:all include ::Zip::NullDecompressor diff --git a/lib/zip/output_stream.rb b/lib/zip/output_stream.rb index 65b7d620..6bfd9382 100644 --- a/lib/zip/output_stream.rb +++ b/lib/zip/output_stream.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip # ZipOutputStream is the basic class for writing zip files. It is # possible to create a ZipOutputStream object directly, passing diff --git a/lib/zip/pass_thru_compressor.rb b/lib/zip/pass_thru_compressor.rb index 2dbaa273..484d0da1 100644 --- a/lib/zip/pass_thru_compressor.rb +++ b/lib/zip/pass_thru_compressor.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip class PassThruCompressor < Compressor #:nodoc:all def initialize(output_stream) diff --git a/lib/zip/pass_thru_decompressor.rb b/lib/zip/pass_thru_decompressor.rb index 22db40e0..c9973c8d 100644 --- a/lib/zip/pass_thru_decompressor.rb +++ b/lib/zip/pass_thru_decompressor.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip class PassThruDecompressor < Decompressor #:nodoc:all def initialize(*args) diff --git a/lib/zip/streamable_directory.rb b/lib/zip/streamable_directory.rb index 3738ce2c..a3c7c93b 100644 --- a/lib/zip/streamable_directory.rb +++ b/lib/zip/streamable_directory.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip class StreamableDirectory < Entry def initialize(zipfile, entry, src_path = nil, permission = nil) diff --git a/lib/zip/streamable_stream.rb b/lib/zip/streamable_stream.rb index 68f3e0e8..e823e002 100644 --- a/lib/zip/streamable_stream.rb +++ b/lib/zip/streamable_stream.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip class StreamableStream < DelegateClass(Entry) # :nodoc:all def initialize(entry) diff --git a/lib/zip/version.rb b/lib/zip/version.rb index 55d4ac29..633899d6 100644 --- a/lib/zip/version.rb +++ b/lib/zip/version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Zip VERSION = '3.0.0' end diff --git a/rubyzip.gemspec b/rubyzip.gemspec index f858ee80..b331648d 100644 --- a/rubyzip.gemspec +++ b/rubyzip.gemspec @@ -1,3 +1,5 @@ +# frozen_string_literal: true + lib = File.expand_path('lib', __dir__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'zip/version' diff --git a/samples/example.rb b/samples/example.rb index 345e7e19..3e82d0a2 100755 --- a/samples/example.rb +++ b/samples/example.rb @@ -1,4 +1,5 @@ #!/usr/bin/env ruby +# frozen_string_literal: true $LOAD_PATH << '../lib' system('zip example.zip example.rb gtk_ruby_zip.rb') diff --git a/samples/example_filesystem.rb b/samples/example_filesystem.rb index 0d93ab6b..d37eaee6 100755 --- a/samples/example_filesystem.rb +++ b/samples/example_filesystem.rb @@ -1,4 +1,5 @@ #!/usr/bin/env ruby +# frozen_string_literal: true $LOAD_PATH << '../lib' diff --git a/samples/example_recursive.rb b/samples/example_recursive.rb index 56a5cc7c..175e69f4 100644 --- a/samples/example_recursive.rb +++ b/samples/example_recursive.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'zip' # This is a simple example which uses rubyzip to diff --git a/samples/gtk_ruby_zip.rb b/samples/gtk_ruby_zip.rb index a86f0a9e..ed8410a2 100755 --- a/samples/gtk_ruby_zip.rb +++ b/samples/gtk_ruby_zip.rb @@ -1,4 +1,5 @@ #!/usr/bin/env ruby +# frozen_string_literal: true $LOAD_PATH << '../lib' diff --git a/samples/qtzip.rb b/samples/qtzip.rb index 2c189ed6..715623a4 100755 --- a/samples/qtzip.rb +++ b/samples/qtzip.rb @@ -1,4 +1,5 @@ #!/usr/bin/env ruby +# frozen_string_literal: true $VERBOSE = true diff --git a/samples/write_simple.rb b/samples/write_simple.rb index 8bb31bb3..f7939200 100755 --- a/samples/write_simple.rb +++ b/samples/write_simple.rb @@ -1,4 +1,5 @@ #!/usr/bin/env ruby +# frozen_string_literal: true $LOAD_PATH << '../lib' diff --git a/samples/zipfind.rb b/samples/zipfind.rb index 8f0dbf2e..c3888389 100755 --- a/samples/zipfind.rb +++ b/samples/zipfind.rb @@ -1,4 +1,5 @@ #!/usr/bin/env ruby +# frozen_string_literal: true $VERBOSE = true diff --git a/test/basic_zip_file_test.rb b/test/basic_zip_file_test.rb index 8ff999bb..160fd208 100644 --- a/test/basic_zip_file_test.rb +++ b/test/basic_zip_file_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class BasicZipFileTest < MiniTest::Test diff --git a/test/bzip2_support_test.rb b/test/bzip2_support_test.rb index ab86b4e8..119d1c0d 100644 --- a/test/bzip2_support_test.rb +++ b/test/bzip2_support_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class Bzip2SupportTest < MiniTest::Test diff --git a/test/case_sensitivity_test.rb b/test/case_sensitivity_test.rb index fdbee8e3..0a9844b1 100644 --- a/test/case_sensitivity_test.rb +++ b/test/case_sensitivity_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class ZipCaseSensitivityTest < MiniTest::Test diff --git a/test/central_directory_entry_test.rb b/test/central_directory_entry_test.rb index c060a4d3..a6898c5c 100644 --- a/test/central_directory_entry_test.rb +++ b/test/central_directory_entry_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class ZipCentralDirectoryEntryTest < MiniTest::Test diff --git a/test/central_directory_test.rb b/test/central_directory_test.rb index 6dfd9ad9..011bdb5f 100644 --- a/test/central_directory_test.rb +++ b/test/central_directory_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class ZipCentralDirectoryTest < MiniTest::Test diff --git a/test/constants_test.rb b/test/constants_test.rb index 8be01715..d31419f5 100644 --- a/test/constants_test.rb +++ b/test/constants_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class ConstantsTest < MiniTest::Test diff --git a/test/crypto/null_encryption_test.rb b/test/crypto/null_encryption_test.rb index ca039962..e6b6ef55 100644 --- a/test/crypto/null_encryption_test.rb +++ b/test/crypto/null_encryption_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class NullEncrypterTest < MiniTest::Test diff --git a/test/crypto/traditional_encryption_test.rb b/test/crypto/traditional_encryption_test.rb index 51f6cbb4..c3cc9fe0 100644 --- a/test/crypto/traditional_encryption_test.rb +++ b/test/crypto/traditional_encryption_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class TraditionalEncrypterTest < MiniTest::Test diff --git a/test/data/notzippedruby.rb b/test/data/notzippedruby.rb index 79f9cbb9..65b52b50 100755 --- a/test/data/notzippedruby.rb +++ b/test/data/notzippedruby.rb @@ -1,4 +1,5 @@ #!/usr/bin/env ruby +# frozen_string_literal: true class NotZippedRuby def return_true diff --git a/test/decompressor_test.rb b/test/decompressor_test.rb index d7ff2e73..9109a2e4 100644 --- a/test/decompressor_test.rb +++ b/test/decompressor_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class DecompressorTest < MiniTest::Test TEST_COMPRESSION_METHOD = 255 diff --git a/test/deflater_test.rb b/test/deflater_test.rb index 35d7b0c6..fb782205 100644 --- a/test/deflater_test.rb +++ b/test/deflater_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class DeflaterTest < MiniTest::Test diff --git a/test/encryption_test.rb b/test/encryption_test.rb index 0b59e64e..110e7a3d 100644 --- a/test/encryption_test.rb +++ b/test/encryption_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class EncryptionTest < MiniTest::Test diff --git a/test/entry_set_test.rb b/test/entry_set_test.rb index fd038deb..4c9f2021 100644 --- a/test/entry_set_test.rb +++ b/test/entry_set_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class ZipEntrySetTest < MiniTest::Test diff --git a/test/entry_test.rb b/test/entry_test.rb index e6ac7a4b..7c1cf60d 100644 --- a/test/entry_test.rb +++ b/test/entry_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class ZipEntryTest < MiniTest::Test diff --git a/test/errors_test.rb b/test/errors_test.rb index 5e6260f8..2f1b506d 100644 --- a/test/errors_test.rb +++ b/test/errors_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class ErrorsTest < MiniTest::Test diff --git a/test/extra_field_test.rb b/test/extra_field_test.rb index c8958fad..7aea91e5 100644 --- a/test/extra_field_test.rb +++ b/test/extra_field_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class ZipExtraFieldTest < MiniTest::Test diff --git a/test/extra_field_ut_test.rb b/test/extra_field_ut_test.rb index 6b854978..74cb21f6 100644 --- a/test/extra_field_ut_test.rb +++ b/test/extra_field_ut_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class ZipExtraFieldUTTest < MiniTest::Test diff --git a/test/file_extract_directory_test.rb b/test/file_extract_directory_test.rb index 02a3fd0d..fc10979d 100644 --- a/test/file_extract_directory_test.rb +++ b/test/file_extract_directory_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class ZipFileExtractDirectoryTest < MiniTest::Test diff --git a/test/file_extract_test.rb b/test/file_extract_test.rb index d8306b35..16fba3a9 100644 --- a/test/file_extract_test.rb +++ b/test/file_extract_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class ZipFileExtractTest < MiniTest::Test diff --git a/test/file_options_test.rb b/test/file_options_test.rb index 61b86e85..e9c612ce 100644 --- a/test/file_options_test.rb +++ b/test/file_options_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class FileOptionsTest < MiniTest::Test @@ -8,9 +10,9 @@ class FileOptionsTest < MiniTest::Test EXTPATH_1 = ::File.join(Dir.tmpdir, 'extracted_1.txt').freeze EXTPATH_2 = ::File.join(Dir.tmpdir, 'extracted_2.txt').freeze EXTPATH_3 = ::File.join(Dir.tmpdir, 'extracted_3.txt').freeze - ENTRY_1 = 'entry_1.txt'.freeze - ENTRY_2 = 'entry_2.txt'.freeze - ENTRY_3 = 'entry_3.txt'.freeze + ENTRY_1 = 'entry_1.txt' + ENTRY_2 = 'entry_2.txt' + ENTRY_3 = 'entry_3.txt' def teardown ::File.unlink(ZIPPATH) if ::File.exist?(ZIPPATH) diff --git a/test/file_permissions_test.rb b/test/file_permissions_test.rb index 2d8283c9..1b31a79b 100644 --- a/test/file_permissions_test.rb +++ b/test/file_permissions_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class FilePermissionsTest < MiniTest::Test diff --git a/test/file_split_test.rb b/test/file_split_test.rb index 22dd1348..50fc4a4b 100644 --- a/test/file_split_test.rb +++ b/test/file_split_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class ZipFileSplitTest < MiniTest::Test diff --git a/test/file_test.rb b/test/file_test.rb index bdba5696..6f1e109c 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class ZipFileTest < MiniTest::Test diff --git a/test/filesystem/dir_iterator_test.rb b/test/filesystem/dir_iterator_test.rb index e46da426..6223b44e 100644 --- a/test/filesystem/dir_iterator_test.rb +++ b/test/filesystem/dir_iterator_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' require 'zip/filesystem' diff --git a/test/filesystem/directory_test.rb b/test/filesystem/directory_test.rb index 8ad04d9e..57177028 100644 --- a/test/filesystem/directory_test.rb +++ b/test/filesystem/directory_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' require 'zip/filesystem' diff --git a/test/filesystem/file_mutating_test.rb b/test/filesystem/file_mutating_test.rb index ccba6e3d..91d45afb 100644 --- a/test/filesystem/file_mutating_test.rb +++ b/test/filesystem/file_mutating_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' require 'zip/filesystem' diff --git a/test/filesystem/file_nonmutating_test.rb b/test/filesystem/file_nonmutating_test.rb index 485298fa..bc0e41d3 100644 --- a/test/filesystem/file_nonmutating_test.rb +++ b/test/filesystem/file_nonmutating_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' require 'zip/filesystem' diff --git a/test/filesystem/file_stat_test.rb b/test/filesystem/file_stat_test.rb index b8efe754..0f6e2b50 100644 --- a/test/filesystem/file_stat_test.rb +++ b/test/filesystem/file_stat_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' require 'zip/filesystem' diff --git a/test/gentestfiles.rb b/test/gentestfiles.rb index bb1b981d..c46e9c22 100755 --- a/test/gentestfiles.rb +++ b/test/gentestfiles.rb @@ -1,4 +1,5 @@ #!/usr/bin/env ruby +# frozen_string_literal: true $VERBOSE = true diff --git a/test/inflater_test.rb b/test/inflater_test.rb index 7748c94f..424d47a3 100644 --- a/test/inflater_test.rb +++ b/test/inflater_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class InflaterTest < MiniTest::Test include DecompressorTests diff --git a/test/input_stream_test.rb b/test/input_stream_test.rb index a1adf10b..2d192abd 100644 --- a/test/input_stream_test.rb +++ b/test/input_stream_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class ZipInputStreamTest < MiniTest::Test diff --git a/test/ioextras/abstract_input_stream_test.rb b/test/ioextras/abstract_input_stream_test.rb index a18c4e3d..6bc87d4d 100644 --- a/test/ioextras/abstract_input_stream_test.rb +++ b/test/ioextras/abstract_input_stream_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' require 'zip/ioextras' diff --git a/test/ioextras/abstract_output_stream_test.rb b/test/ioextras/abstract_output_stream_test.rb index 8c612445..d4a8d0a8 100644 --- a/test/ioextras/abstract_output_stream_test.rb +++ b/test/ioextras/abstract_output_stream_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' require 'zip/ioextras' diff --git a/test/ioextras/fake_io_test.rb b/test/ioextras/fake_io_test.rb index 612f442f..07b24b5a 100644 --- a/test/ioextras/fake_io_test.rb +++ b/test/ioextras/fake_io_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' require 'zip/ioextras' diff --git a/test/local_entry_test.rb b/test/local_entry_test.rb index 1317fb45..9d2e3ed5 100644 --- a/test/local_entry_test.rb +++ b/test/local_entry_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class ZipLocalEntryTest < MiniTest::Test diff --git a/test/output_stream_test.rb b/test/output_stream_test.rb index c8139456..3b46f615 100644 --- a/test/output_stream_test.rb +++ b/test/output_stream_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class ZipOutputStreamTest < MiniTest::Test diff --git a/test/pass_thru_compressor_test.rb b/test/pass_thru_compressor_test.rb index 334ba90c..455f800b 100644 --- a/test/pass_thru_compressor_test.rb +++ b/test/pass_thru_compressor_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class PassThruCompressorTest < MiniTest::Test diff --git a/test/pass_thru_decompressor_test.rb b/test/pass_thru_decompressor_test.rb index e0b66892..937c3ac6 100644 --- a/test/pass_thru_decompressor_test.rb +++ b/test/pass_thru_decompressor_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class PassThruDecompressorTest < MiniTest::Test include DecompressorTests diff --git a/test/path_traversal_test.rb b/test/path_traversal_test.rb index 47c7e30f..9676432b 100644 --- a/test/path_traversal_test.rb +++ b/test/path_traversal_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class PathTraversalTest < MiniTest::Test diff --git a/test/samples/example_recursive_test.rb b/test/samples/example_recursive_test.rb index 4fb14883..a2af3ec7 100644 --- a/test/samples/example_recursive_test.rb +++ b/test/samples/example_recursive_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' require 'fileutils' require_relative '../../samples/example_recursive' diff --git a/test/settings_test.rb b/test/settings_test.rb index 0510a6fc..6e33573c 100644 --- a/test/settings_test.rb +++ b/test/settings_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class ZipSettingsTest < MiniTest::Test diff --git a/test/stored_support_test.rb b/test/stored_support_test.rb index 28836b9e..1a125916 100644 --- a/test/stored_support_test.rb +++ b/test/stored_support_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class StoredSupportTest < MiniTest::Test diff --git a/test/test_helper.rb b/test/test_helper.rb index 212a24c0..125ee99c 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'simplecov' require 'minitest/autorun' require 'minitest/unit' diff --git a/test/unicode_file_names_and_comments_test.rb b/test/unicode_file_names_and_comments_test.rb index 7950e289..1518171a 100644 --- a/test/unicode_file_names_and_comments_test.rb +++ b/test/unicode_file_names_and_comments_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class ZipUnicodeFileNamesAndComments < MiniTest::Test diff --git a/test/zip64_full_test.rb b/test/zip64_full_test.rb index ed11ed65..769709e9 100644 --- a/test/zip64_full_test.rb +++ b/test/zip64_full_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + if ENV['FULL_ZIP64_TEST'] require 'minitest/autorun' require 'minitest/unit' diff --git a/test/zip64_support_test.rb b/test/zip64_support_test.rb index 3e4154a8..0e96a9c6 100644 --- a/test/zip64_support_test.rb +++ b/test/zip64_support_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class Zip64SupportTest < MiniTest::Test From 606b5ffbb2ec58db61527946b468145ec828a7bd Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 23 May 2021 20:18:50 +0100 Subject: [PATCH 235/469] Fix Lint/EmptyBlock cop. --- .rubocop_todo.yml | 8 -------- lib/zip/file.rb | 3 +-- test/gentestfiles.rb | 4 ++-- test/settings_test.rb | 6 ++---- 4 files changed, 5 insertions(+), 16 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b120cadf..5a4db7b0 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -23,14 +23,6 @@ Layout/EmptyLinesAroundAttributeAccessor: Layout/FirstArrayElementIndentation: EnforcedStyle: consistent -# Offense count: 5 -# Configuration parameters: AllowComments, AllowEmptyLambdas. -Lint/EmptyBlock: - Exclude: - - 'lib/zip/file.rb' - - 'test/gentestfiles.rb' - - 'test/settings_test.rb' - # Offense count: 3 # Configuration parameters: AllowComments. Lint/EmptyClass: diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 1c579539..2efc8cd3 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -242,8 +242,7 @@ def split(zip_file_name, segment_size = MAX_SEGMENT_SIZE, delete_zip_file = true return if zip_file_size <= segment_size segment_count = get_segment_count_for_split(zip_file_size, segment_size) - # Checking for correct zip structure - ::Zip::File.open(zip_file_name) {} + ::Zip::File.open(zip_file_name) {} # Check for correct zip structure. partial_zip_file_name = get_partial_zip_file_name(zip_file_name, partial_zip_file_name) szip_file_index = 0 ::File.open(zip_file_name, 'rb') do |zip_file| diff --git a/test/gentestfiles.rb b/test/gentestfiles.rb index c46e9c22..756344e3 100755 --- a/test/gentestfiles.rb +++ b/test/gentestfiles.rb @@ -80,8 +80,8 @@ def self.create_test_zips "zip -q #{TEST_ZIP1.zip_name} -d test/data/file2.txt" ) - File.open('test/data/generated/empty.txt', 'w') {} - File.open('test/data/generated/empty_chmod640.txt', 'w') {} + File.open('test/data/generated/empty.txt', 'w') {} # Empty file. + File.open('test/data/generated/empty_chmod640.txt', 'w') {} # Empty file. ::File.chmod(0o640, 'test/data/generated/empty_chmod640.txt') File.open('test/data/generated/short.txt', 'w') { |file| file << 'ABCDEF' } diff --git a/test/settings_test.rb b/test/settings_test.rb index 6e33573c..a0c6906d 100644 --- a/test/settings_test.rb +++ b/test/settings_test.rb @@ -73,8 +73,7 @@ def test_false_warn_invalid_date Zip.warn_invalid_date = false assert_output('', '') do - ::Zip::File.open(test_file) do |_zf| - end + ::Zip::File.open(test_file) {} # Do nothing with the open file. end end @@ -83,8 +82,7 @@ def test_true_warn_invalid_date Zip.warn_invalid_date = true assert_output('', /invalid date\/time in zip entry/) do - ::Zip::File.open(test_file) do |_zf| - end + ::Zip::File.open(test_file) {} # Do nothing with the open file. end end From deac4fa3139e7c254d41615197f065f793dcb712 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 23 May 2021 20:47:04 +0100 Subject: [PATCH 236/469] Fix Style/CommentAnnotation cop. --- .rubocop_todo.yml | 9 --------- test/file_test.rb | 2 +- test/zip64_full_test.rb | 2 +- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 5a4db7b0..b98ae8cf 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -107,15 +107,6 @@ Style/ClassAndModuleChildren: - 'lib/zip/extra_field/zip64.rb' - 'lib/zip/extra_field/zip64_placeholder.rb' -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: Keywords. -# Keywords: TODO, FIXME, OPTIMIZE, HACK, REVIEW, NOTE -Style/CommentAnnotation: - Exclude: - - 'test/file_test.rb' - - 'test/zip64_full_test.rb' - # Offense count: 1 # Cop supports --auto-correct. Style/Dir: diff --git a/test/file_test.rb b/test/file_test.rb index 6f1e109c..a24611b6 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -134,7 +134,7 @@ def test_open_buffer_no_op_does_not_change_file test_zip = File.join(tmp, 'test.zip') FileUtils.cp 'test/data/rubycode.zip', test_zip - # Note: this may change the file if it is opened with r+b instead of rb. + # NOTE: this may change the file if it is opened with r+b instead of rb. # The 'extra fields' in this particular zip file get reordered. File.open(test_zip, 'rb') do |file| Zip::File.open_buffer(file) do diff --git a/test/zip64_full_test.rb b/test/zip64_full_test.rb index 769709e9..6f900bb4 100644 --- a/test/zip64_full_test.rb +++ b/test/zip64_full_test.rb @@ -43,7 +43,7 @@ def test_large_zip_file assert_equal last_text, zf.read('last_file.txt') end - # note: if this fails, be sure you have UnZip version 6.0 or newer + # NOTE: if this fails, be sure you have UnZip version 6.0 or newer # as this is the first version to support zip64 extensions # but some OSes (*cough* OSX) still bundle a 5.xx release assert system("unzip -tqq #{test_filename}"), 'third-party zip validation failed' From e2c16991e5e70e457cb5bb15533c3f8166169f63 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 23 May 2021 20:51:35 +0100 Subject: [PATCH 237/469] Fix Style/Dir cop. --- .rubocop_todo.yml | 6 ------ test/file_test.rb | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b98ae8cf..c92dcad9 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -107,12 +107,6 @@ Style/ClassAndModuleChildren: - 'lib/zip/extra_field/zip64.rb' - 'lib/zip/extra_field/zip64_placeholder.rb' -# Offense count: 1 -# Cop supports --auto-correct. -Style/Dir: - Exclude: - - 'test/file_test.rb' - # Offense count: 2 Style/DocumentDynamicEvalDefinition: Exclude: diff --git a/test/file_test.rb b/test/file_test.rb index a24611b6..3a9e903a 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -687,7 +687,7 @@ def test_preserve_file_order end def test_streaming - fname = ::File.join(::File.expand_path(::File.dirname(__FILE__)), '../README.md') + fname = ::File.join(__dir__, '..', 'README.md') zname = 'test/data/generated/README.zip' Zip::File.open(zname, Zip::File::CREATE) do |zipfile| zipfile.get_output_stream(File.basename(fname)) do |f| From 8fa35de52892756fc8d8e3ed56830c2130d4d26c Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 23 May 2021 20:59:27 +0100 Subject: [PATCH 238/469] Fix Style/FormatStringToken cop. --- .rubocop_todo.yml | 6 ------ test/ioextras/abstract_output_stream_test.rb | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index c92dcad9..fac7500e 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -117,12 +117,6 @@ Style/DocumentDynamicEvalDefinition: Style/Documentation: Enabled: false -# Offense count: 2 -# Configuration parameters: MaxUnannotatedPlaceholdersAllowed, IgnoredMethods. -# SupportedStyles: annotated, template, unannotated -Style/FormatStringToken: - EnforcedStyle: unannotated - # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: AllowSplatArgument. diff --git a/test/ioextras/abstract_output_stream_test.rb b/test/ioextras/abstract_output_stream_test.rb index d4a8d0a8..80218ec7 100644 --- a/test/ioextras/abstract_output_stream_test.rb +++ b/test/ioextras/abstract_output_stream_test.rb @@ -71,7 +71,7 @@ def test_print end def test_printf - @output_stream.printf('%d %04x', 123, 123) + @output_stream.printf('%d %04x', dec: 123, hex: 123) assert_equal('123 007b', @output_stream.buffer) end From 1b3f4bb7b8708cc962b27226cf3a8a1879d89625 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 23 May 2021 21:05:46 +0100 Subject: [PATCH 239/469] Fix Style/HashConversion cop. --- .rubocop_todo.yml | 7 ------- lib/zip/entry_set.rb | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index fac7500e..e9cb7f22 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -117,13 +117,6 @@ Style/DocumentDynamicEvalDefinition: Style/Documentation: Enabled: false -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: AllowSplatArgument. -Style/HashConversion: - Exclude: - - 'lib/zip/entry_set.rb' - # Offense count: 13 # Cop supports --auto-correct. Style/IfUnlessModifier: diff --git a/lib/zip/entry_set.rb b/lib/zip/entry_set.rb index a5822453..530ef7ef 100644 --- a/lib/zip/entry_set.rb +++ b/lib/zip/entry_set.rb @@ -72,7 +72,7 @@ def glob(pattern, flags = ::File::FNM_PATHNAME | ::File::FNM_DOTMATCH | ::File:: protected def sorted_entries - ::Zip.sort_entries ? Hash[@entry_set.sort] : @entry_set + ::Zip.sort_entries ? @entry_set.sort.to_h : @entry_set end private From efa23a84baf1ca52795f80d7c143404e57c02d05 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 23 May 2021 21:08:53 +0100 Subject: [PATCH 240/469] Fix Style/RedundantBegin cop. --- .rubocop_todo.yml | 6 ------ lib/zip/dos_time.rb | 5 ++--- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index e9cb7f22..d1f7b409 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -176,12 +176,6 @@ Style/OptionalBooleanParameter: - 'lib/zip/file.rb' - 'lib/zip/output_stream.rb' -# Offense count: 1 -# Cop supports --auto-correct. -Style/RedundantBegin: - Exclude: - - 'lib/zip/dos_time.rb' - # Offense count: 1 # Cop supports --auto-correct. Style/RedundantFileExtensionInRequire: diff --git a/lib/zip/dos_time.rb b/lib/zip/dos_time.rb index 264ead28..9203d734 100644 --- a/lib/zip/dos_time.rb +++ b/lib/zip/dos_time.rb @@ -52,9 +52,8 @@ def self.parse_binary_dos_format(bin_dos_date, bin_dos_time) day = (0b11111 & bin_dos_date) month = (0b111100000 & bin_dos_date) >> 5 year = ((0b1111111000000000 & bin_dos_date) >> 9) + 1980 - begin - local(year, month, day, hour, minute, second) - end + + local(year, month, day, hour, minute, second) end if defined? JRUBY_VERSION && Gem::Version.new(JRUBY_VERSION) < '9.2.18.0' From 2b04cc26fa75fc0fbdb266b8957fdbe690bfaa06 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 23 May 2021 21:11:07 +0100 Subject: [PATCH 241/469] Fix Style/RedundantFileExtensionInRequire cop. --- .rubocop_todo.yml | 6 ------ samples/qtzip.rb | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index d1f7b409..ee5adfa8 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -176,12 +176,6 @@ Style/OptionalBooleanParameter: - 'lib/zip/file.rb' - 'lib/zip/output_stream.rb' -# Offense count: 1 -# Cop supports --auto-correct. -Style/RedundantFileExtensionInRequire: - Exclude: - - 'samples/qtzip.rb' - # Offense count: 29 # Cop supports --auto-correct. Style/RedundantRegexpEscape: diff --git a/samples/qtzip.rb b/samples/qtzip.rb index 715623a4..51e084cc 100755 --- a/samples/qtzip.rb +++ b/samples/qtzip.rb @@ -7,7 +7,7 @@ require 'Qt' system('rbuic -o zipdialogui.rb zipdialogui.ui') -require 'zipdialogui.rb' +require 'zipdialogui' require 'zip' a = Qt::Application.new(ARGV) From e64132f4fc682b7e8d9327ebe9462beb3b16242e Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 23 May 2021 21:14:33 +0100 Subject: [PATCH 242/469] Fix Style/NegatedIfElseCondition cop. --- .rubocop_todo.yml | 6 ------ samples/qtzip.rb | 8 ++++---- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index ee5adfa8..e994fee0 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -142,12 +142,6 @@ Style/ModuleFunction: Style/MutableConstant: Enabled: false -# Offense count: 2 -# Cop supports --auto-correct. -Style/NegatedIfElseCondition: - Exclude: - - 'samples/qtzip.rb' - # Offense count: 24 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, IgnoredMethods. diff --git a/samples/qtzip.rb b/samples/qtzip.rb index 51e084cc..917255fd 100755 --- a/samples/qtzip.rb +++ b/samples/qtzip.rb @@ -66,14 +66,14 @@ def extract_files end puts "selected_items.size = #{selected_items.size}" puts "unselected_items.size = #{unselected_items.size}" - items = !selected_items.empty? ? selected_items : unselected_items + items = selected_items.empty? ? unselected_items : selected_items puts "items.size = #{items.size}" d = Qt::FileDialog.get_existing_directory(nil, self) - if !d - puts 'No directory chosen' - else + if d zipfile { |zf| items.each { |e| zf.extract(e, File.join(d, e)) } } + else + puts 'No directory chosen' end end From 57fa5013c051780e5d5d21e246e228f15053f2b3 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 23 May 2021 21:28:33 +0100 Subject: [PATCH 243/469] Turn off Lint/EmptyClass cop. --- .rubocop.yml | 3 +++ .rubocop_todo.yml | 7 ------- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index a51a3608..f6f132e2 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -22,6 +22,9 @@ Layout/LineLength: Exclude: - 'test/**/*.rb' +Lint/EmptyClass: + Enabled: false + # In some cases we just need to catch an exception, rather than # actually handle it. Allow the tests to make use of this shortcut. Lint/SuppressedException: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index e994fee0..9c650fad 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -23,13 +23,6 @@ Layout/EmptyLinesAroundAttributeAccessor: Layout/FirstArrayElementIndentation: EnforcedStyle: consistent -# Offense count: 3 -# Configuration parameters: AllowComments. -Lint/EmptyClass: - Exclude: - - 'lib/zip/crypto/encryption.rb' - - 'test/decompressor_test.rb' - # Offense count: 7 Lint/MissingSuper: Exclude: From deabe0279862bb68aac6956f5bcdbcb188e2d141 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 23 May 2021 21:36:27 +0100 Subject: [PATCH 244/469] Fix Layout/FirstArrayElementIndentation cop. --- .rubocop_todo.yml | 7 ------- .simplecov | 10 ++++++---- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 9c650fad..19905951 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -16,13 +16,6 @@ Layout/EmptyLinesAroundAttributeAccessor: - 'lib/zip/filesystem.rb' - 'samples/gtk_ruby_zip.rb' -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: IndentationWidth. -# SupportedStyles: special_inside_parentheses, consistent, align_brackets -Layout/FirstArrayElementIndentation: - EnforcedStyle: consistent - # Offense count: 7 Lint/MissingSuper: Exclude: diff --git a/.simplecov b/.simplecov index 6c400bf6..611a9527 100644 --- a/.simplecov +++ b/.simplecov @@ -9,10 +9,12 @@ SimpleCov::Formatter::LcovFormatter.config do |c| c.single_report_path = 'coverage/lcov.info' end -SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([ - SimpleCov::Formatter::HTMLFormatter, - SimpleCov::Formatter::LcovFormatter -]) +SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new( + [ + SimpleCov::Formatter::HTMLFormatter, + SimpleCov::Formatter::LcovFormatter + ] +) SimpleCov.start do # enable_coverage :branch <-- Re-enable this when we move to ruby ~> 2.5. From fe998a5aecda44db180dd6b138ce35d8446af9ea Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 23 May 2021 21:43:11 +0100 Subject: [PATCH 245/469] Fix Layout/EmptyLinesAroundAttributeAccessor cop. --- .rubocop_todo.yml | 10 ---------- lib/zip/extra_field/zip64.rb | 4 +++- lib/zip/filesystem.rb | 1 - samples/gtk_ruby_zip.rb | 3 ++- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 19905951..c49b22d6 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,16 +6,6 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 3 -# Cop supports --auto-correct. -# Configuration parameters: AllowAliasSyntax, AllowedMethods. -# AllowedMethods: alias_method, public, protected, private -Layout/EmptyLinesAroundAttributeAccessor: - Exclude: - - 'lib/zip/extra_field/zip64.rb' - - 'lib/zip/filesystem.rb' - - 'samples/gtk_ruby_zip.rb' - # Offense count: 7 Lint/MissingSuper: Exclude: diff --git a/lib/zip/extra_field/zip64.rb b/lib/zip/extra_field/zip64.rb index 2f7c32c9..0e73ce0d 100644 --- a/lib/zip/extra_field/zip64.rb +++ b/lib/zip/extra_field/zip64.rb @@ -3,7 +3,9 @@ module Zip # Info-ZIP Extra for Zip64 size class ExtraField::Zip64 < ExtraField::Generic - attr_accessor :original_size, :compressed_size, :relative_header_offset, :disk_start_number + attr_accessor :compressed_size, :disk_start_number, + :original_size, :relative_header_offset + HEADER_ID = ['0100'].pack('H*') register_map diff --git a/lib/zip/filesystem.rb b/lib/zip/filesystem.rb index 04162ca0..2815069b 100644 --- a/lib/zip/filesystem.rb +++ b/lib/zip/filesystem.rb @@ -66,7 +66,6 @@ def file # similarity with the methods in File class ZipFsFile attr_writer :dir - # protected :dir class ZipFsStat class << self diff --git a/samples/gtk_ruby_zip.rb b/samples/gtk_ruby_zip.rb index ed8410a2..674e8e77 100755 --- a/samples/gtk_ruby_zip.rb +++ b/samples/gtk_ruby_zip.rb @@ -43,7 +43,8 @@ def initialize end class ButtonPanel < Gtk::HButtonBox - attr_reader :open_button, :extract_button + attr_reader :extract_button, :open_button + def initialize super set_layout(Gtk::BUTTONBOX_START) From 3131e6a4aa90b1f887d8c876aa0b3a231488d2ae Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 23 May 2021 21:59:18 +0100 Subject: [PATCH 246/469] Fix/configure Naming/VariableNumber cop. --- .rubocop.yml | 5 +++++ .rubocop_todo.yml | 9 --------- test/file_extract_test.rb | 2 +- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index f6f132e2..4ef52bd1 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -59,6 +59,11 @@ Metrics/MethodLength: Exclude: - 'test/**/*.rb' +# These tests are just better with snake_case numbers. +Naming/VariableNumber: + Exclude: + - 'test/file_permissions_test.rb' + # Set a consistent way of checking types. Style/ClassCheck: EnforcedStyle: kind_of? diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index c49b22d6..8a404912 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -51,15 +51,6 @@ Naming/AccessorMethodName: - 'lib/zip/input_stream.rb' - 'lib/zip/streamable_stream.rb' -# Offense count: 4 -# Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers. -# SupportedStyles: snake_case, normalcase, non_integer -# AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339 -Naming/VariableNumber: - Exclude: - - 'test/file_extract_test.rb' - - 'test/file_permissions_test.rb' - # Offense count: 4 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. diff --git a/test/file_extract_test.rb b/test/file_extract_test.rb index 16fba3a9..3df4308b 100644 --- a/test/file_extract_test.rb +++ b/test/file_extract_test.rb @@ -75,7 +75,7 @@ def test_extract_non_entry zf.close if zf end - def test_extract_non_entry_2 + def test_extract_another_non_entry out_file = 'outfile' assert_raises(Errno::ENOENT) do zf = ::Zip::File.new(TEST_ZIP.zip_name) From 55ed74c20e721a8440655407ec6be75c1d0f5831 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 23 May 2021 22:03:33 +0100 Subject: [PATCH 247/469] Fix/configure Style/AccessorGrouping cop. --- .rubocop.yml | 5 +++++ .rubocop_todo.yml | 9 --------- lib/zip/decompressor.rb | 3 +-- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 4ef52bd1..643cb498 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -64,6 +64,11 @@ Naming/VariableNumber: Exclude: - 'test/file_permissions_test.rb' +# Need to allow accessors in Entry to be separated for doc purposes. +Style/AccessorGrouping: + Exclude: + - 'lib/zip/entry.rb' + # Set a consistent way of checking types. Style/ClassCheck: EnforcedStyle: kind_of? diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 8a404912..38e57302 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -51,15 +51,6 @@ Naming/AccessorMethodName: - 'lib/zip/input_stream.rb' - 'lib/zip/streamable_stream.rb' -# Offense count: 4 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: separated, grouped -Style/AccessorGrouping: - Exclude: - - 'lib/zip/decompressor.rb' - - 'lib/zip/entry.rb' - # Offense count: 7 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. diff --git a/lib/zip/decompressor.rb b/lib/zip/decompressor.rb index 0d29e618..b6bb9cc8 100644 --- a/lib/zip/decompressor.rb +++ b/lib/zip/decompressor.rb @@ -16,8 +16,7 @@ def self.find_by_compression_method(compression_method) decompressor_classes[compression_method] end - attr_reader :input_stream - attr_reader :decompressed_size + attr_reader :decompressed_size, :input_stream def initialize(input_stream, decompressed_size = nil) super() From 984d86ce4baf3ab3e2c12671326a3e49d8f7cfe8 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 24 May 2021 14:24:01 +0100 Subject: [PATCH 248/469] Configure Style/ModuleFunction cop. --- .rubocop.yml | 5 +++++ .rubocop_todo.yml | 8 -------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 643cb498..d9fa5a77 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -79,6 +79,11 @@ Style/HashEachMethods: Style/HashTransformValues: Enabled: true +# Allow non-default behaviour for Zip. +Style/ModuleFunction: + Exclude: + - 'lib/zip.rb' + # Allow this multi-line block chain as it actually reads better # than the alternatives. Style/MultilineBlockChain: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 38e57302..6eda637a 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -85,14 +85,6 @@ Style/IfUnlessModifier: - 'lib/zip/pass_thru_decompressor.rb' - 'lib/zip/streamable_stream.rb' -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, Autocorrect. -# SupportedStyles: module_function, extend_self, forbidden -Style/ModuleFunction: - Exclude: - - 'lib/zip.rb' - # Offense count: 56 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. From f6cebc651440c708d48ea2a05501e0f422d1d606 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 24 May 2021 20:58:54 +0100 Subject: [PATCH 249/469] Add rubocop-rake. --- .rubocop.yml | 3 +++ rubyzip.gemspec | 1 + 2 files changed, 4 insertions(+) diff --git a/.rubocop.yml b/.rubocop.yml index d9fa5a77..7c84a9fb 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,3 +1,6 @@ +require: + - rubocop-rake + inherit_from: .rubocop_todo.yml # Set this to the minimum supported ruby in the gemspec. Otherwise diff --git a/rubyzip.gemspec b/rubyzip.gemspec index b331648d..2469f54c 100644 --- a/rubyzip.gemspec +++ b/rubyzip.gemspec @@ -26,6 +26,7 @@ Gem::Specification.new do |s| s.add_development_dependency 'minitest', '~> 5.4' s.add_development_dependency 'rake', '~> 12.3.3' s.add_development_dependency 'rubocop', '~> 1.12.0' + s.add_development_dependency 'rubocop-rake', '~> 0.5.0' s.add_development_dependency 'simplecov', '~> 0.18.0' s.add_development_dependency 'simplecov-lcov', '~> 0.8' end From 255480f22f37527fda47460a2527da7c36772deb Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 24 May 2021 21:15:19 +0100 Subject: [PATCH 250/469] Add rubocop-performance. --- .rubocop.yml | 1 + .rubocop_todo.yml | 40 +++++++++++++++++++++++++++++++++++++--- rubyzip.gemspec | 1 + 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 7c84a9fb..7f1fc683 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,4 +1,5 @@ require: + - rubocop-performance - rubocop-rake inherit_from: .rubocop_todo.yml diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 6eda637a..67b40a52 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2021-05-23 16:31:53 UTC using RuboCop version 1.12.1. +# on 2021-05-24 20:07:32 UTC using RuboCop version 1.12.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -51,6 +51,31 @@ Naming/AccessorMethodName: - 'lib/zip/input_stream.rb' - 'lib/zip/streamable_stream.rb' +# Offense count: 3 +# Cop supports --auto-correct. +Performance/BlockGivenWithExplicitBlock: + Exclude: + - 'lib/zip/entry.rb' + +# Offense count: 2 +Performance/FixedSize: + Exclude: + - 'test/ioextras/abstract_output_stream_test.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +Performance/RegexpMatch: + Exclude: + - 'test/file_test.rb' + - 'test/gentestfiles.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: AutoCorrect. +Performance/StringInclude: + Exclude: + - 'test/file_test.rb' + # Offense count: 7 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. @@ -85,12 +110,21 @@ Style/IfUnlessModifier: - 'lib/zip/pass_thru_decompressor.rb' - 'lib/zip/streamable_stream.rb' -# Offense count: 56 +# Offense count: 12 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: literals, strict Style/MutableConstant: - Enabled: false + Exclude: + - 'lib/zip/extra_field.rb' + - 'lib/zip/file.rb' + - 'lib/zip/ioextras.rb' + - 'test/case_sensitivity_test.rb' + - 'test/entry_set_test.rb' + - 'test/extra_field_ut_test.rb' + - 'test/filesystem/dir_iterator_test.rb' + - 'test/gentestfiles.rb' + - 'test/ioextras/abstract_input_stream_test.rb' # Offense count: 24 # Cop supports --auto-correct. diff --git a/rubyzip.gemspec b/rubyzip.gemspec index 2469f54c..295601a0 100644 --- a/rubyzip.gemspec +++ b/rubyzip.gemspec @@ -26,6 +26,7 @@ Gem::Specification.new do |s| s.add_development_dependency 'minitest', '~> 5.4' s.add_development_dependency 'rake', '~> 12.3.3' s.add_development_dependency 'rubocop', '~> 1.12.0' + s.add_development_dependency 'rubocop-performance', '~> 1.10.0' s.add_development_dependency 'rubocop-rake', '~> 0.5.0' s.add_development_dependency 'simplecov', '~> 0.18.0' s.add_development_dependency 'simplecov-lcov', '~> 0.8' From 530afe5d0c63e703c4a1e45010e42627b66e75d2 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 24 May 2021 21:50:20 +0100 Subject: [PATCH 251/469] Fix Performance/BlockGivenWithExplicitBlock cop. --- .rubocop_todo.yml | 6 ------ lib/zip/entry.rb | 6 +++--- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 67b40a52..44c55efb 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -51,12 +51,6 @@ Naming/AccessorMethodName: - 'lib/zip/input_stream.rb' - 'lib/zip/streamable_stream.rb' -# Offense count: 3 -# Cop supports --auto-correct. -Performance/BlockGivenWithExplicitBlock: - Exclude: - - 'lib/zip/entry.rb' - # Offense count: 2 Performance/FixedSize: Exclude: diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 453c174b..08e10cd0 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -547,7 +547,7 @@ def <=>(other) # Warning: may behave weird with symlinks. def get_input_stream(&block) if @ftype == :directory - yield ::Zip::NullInputStream if block_given? + yield ::Zip::NullInputStream if block ::Zip::NullInputStream elsif @filepath case @ftype @@ -556,7 +556,7 @@ def get_input_stream(&block) when :symlink linkpath = ::File.readlink(@filepath) stringio = ::StringIO.new(linkpath) - yield(stringio) if block_given? + yield(stringio) if block stringio else raise "unknown @file_type #{@ftype}" @@ -565,7 +565,7 @@ def get_input_stream(&block) zis = ::Zip::InputStream.new(@zipfile, local_header_offset) zis.instance_variable_set(:@complete_entry, self) zis.get_next_entry - if block_given? + if block begin yield(zis) ensure From ed21f9cf17cca90a1200ee201204edcad47c2753 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 24 May 2021 21:55:40 +0100 Subject: [PATCH 252/469] Fix Performance/StringInclude cop. --- .rubocop_todo.yml | 7 ------- test/file_test.rb | 4 ++-- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 44c55efb..9074359d 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -63,13 +63,6 @@ Performance/RegexpMatch: - 'test/file_test.rb' - 'test/gentestfiles.rb' -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: AutoCorrect. -Performance/StringInclude: - Exclude: - - 'test/file_test.rb' - # Offense count: 7 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. diff --git a/test/file_test.rb b/test/file_test.rb index 3a9e903a..52fe356e 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -699,14 +699,14 @@ def test_streaming File.open(zname, 'rb') do |f| Zip::File.open_buffer(f) do |zipfile| zipfile.each do |entry| - next unless entry.name =~ /README.md/ + next unless entry.name.include?('README.md') data = zipfile.read(entry) end end end assert data - assert data =~ /Simonov/ + assert data.include?('Simonov') end def test_nonexistant_zip From 7af12ca887e6215bb044e31a95820d9c60a8bccc Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 24 May 2021 21:59:26 +0100 Subject: [PATCH 253/469] Fix Performance/FixedSize cop. --- .rubocop_todo.yml | 5 ----- test/ioextras/abstract_output_stream_test.rb | 16 +++++++++------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 9074359d..b196cfe9 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -51,11 +51,6 @@ Naming/AccessorMethodName: - 'lib/zip/input_stream.rb' - 'lib/zip/streamable_stream.rb' -# Offense count: 2 -Performance/FixedSize: - Exclude: - - 'test/ioextras/abstract_output_stream_test.rb' - # Offense count: 2 # Cop supports --auto-correct. Performance/RegexpMatch: diff --git a/test/ioextras/abstract_output_stream_test.rb b/test/ioextras/abstract_output_stream_test.rb index 80218ec7..619107c0 100644 --- a/test/ioextras/abstract_output_stream_test.rb +++ b/test/ioextras/abstract_output_stream_test.rb @@ -32,13 +32,15 @@ def teardown end def test_write - count = @output_stream.write('a little string') - assert_equal('a little string', @output_stream.buffer) - assert_equal('a little string'.length, count) - - count = @output_stream.write('. a little more') - assert_equal('a little string. a little more', @output_stream.buffer) - assert_equal('. a little more'.length, count) + str1 = 'a little string' + count = @output_stream.write(str1) + assert_equal(str1, @output_stream.buffer) + assert_equal(str1.length, count) + + str2 = '. a little more' + count = @output_stream.write(str2) + assert_equal(str1 + str2, @output_stream.buffer) + assert_equal(str2.length, count) end def test_print From 86758175f09dca80771f19fbefd02a1e81e50b5d Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 24 May 2021 22:02:39 +0100 Subject: [PATCH 254/469] Fix Performance/RegexpMatch cop. --- .rubocop_todo.yml | 7 ------- test/gentestfiles.rb | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b196cfe9..61eb9caf 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -51,13 +51,6 @@ Naming/AccessorMethodName: - 'lib/zip/input_stream.rb' - 'lib/zip/streamable_stream.rb' -# Offense count: 2 -# Cop supports --auto-correct. -Performance/RegexpMatch: - Exclude: - - 'test/file_test.rb' - - 'test/gentestfiles.rb' - # Offense count: 7 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. diff --git a/test/gentestfiles.rb b/test/gentestfiles.rb index 756344e3..66818694 100755 --- a/test/gentestfiles.rb +++ b/test/gentestfiles.rb @@ -106,7 +106,7 @@ def self.create_test_zips "zip -q #{TEST_ZIP2.zip_name} #{TEST_ZIP2.entry_names.join(' ')}" ) - if RUBY_PLATFORM =~ /mswin|mingw|cygwin/ + if RUBY_PLATFORM.match?(/mswin|mingw|cygwin/) raise "failed to add comment to test zip '#{TEST_ZIP2.zip_name}'" \ unless system( "echo #{TEST_ZIP2.comment}| zip -zq #{TEST_ZIP2.zip_name}\"" From ca516df01e6f72d6e6fb87846619e6c5cda0b0ae Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 25 May 2021 19:42:05 +0100 Subject: [PATCH 255/469] Remove stale Style/DocumentDynamicEvalDefinition cop. --- .rubocop_todo.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 61eb9caf..ac505127 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -65,11 +65,6 @@ Style/ClassAndModuleChildren: - 'lib/zip/extra_field/zip64.rb' - 'lib/zip/extra_field/zip64_placeholder.rb' -# Offense count: 2 -Style/DocumentDynamicEvalDefinition: - Exclude: - - 'test/test_helper.rb' - # Offense count: 24 # Configuration parameters: AllowedConstants. Style/Documentation: From ce08405c1afb874569e207cb094df1e757e3de9d Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 25 May 2021 21:50:06 +0100 Subject: [PATCH 256/469] Fix (most) Style/MutableConstant cop errors. The last one, in `ExtraField` needs a sizeable refactor to fix. --- .rubocop_todo.yml | 10 +--------- lib/zip/file.rb | 2 +- lib/zip/ioextras.rb | 2 -- test/case_sensitivity_test.rb | 6 ++++-- test/entry_set_test.rb | 2 +- test/extra_field_ut_test.rb | 2 +- test/filesystem/dir_iterator_test.rb | 2 +- test/gentestfiles.rb | 8 +++++--- test/ioextras/abstract_input_stream_test.rb | 11 +++++++---- 9 files changed, 21 insertions(+), 24 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index ac505127..fffe19f4 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -80,21 +80,13 @@ Style/IfUnlessModifier: - 'lib/zip/pass_thru_decompressor.rb' - 'lib/zip/streamable_stream.rb' -# Offense count: 12 +# Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: literals, strict Style/MutableConstant: Exclude: - 'lib/zip/extra_field.rb' - - 'lib/zip/file.rb' - - 'lib/zip/ioextras.rb' - - 'test/case_sensitivity_test.rb' - - 'test/entry_set_test.rb' - - 'test/extra_field_ut_test.rb' - - 'test/filesystem/dir_iterator_test.rb' - - 'test/gentestfiles.rb' - - 'test/ioextras/abstract_input_stream_test.rb' # Offense count: 24 # Cop supports --auto-correct. diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 2efc8cd3..10a3f495 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -51,7 +51,7 @@ class File < CentralDirectory MAX_SEGMENT_SIZE = 3_221_225_472 MIN_SEGMENT_SIZE = 65_536 DATA_BUFFER_SIZE = 8192 - IO_METHODS = [:tell, :seek, :read, :eof, :close] + IO_METHODS = [:tell, :seek, :read, :eof, :close].freeze DEFAULT_OPTIONS = { restore_ownership: false, diff --git a/lib/zip/ioextras.rb b/lib/zip/ioextras.rb index 879a5145..df208da1 100644 --- a/lib/zip/ioextras.rb +++ b/lib/zip/ioextras.rb @@ -4,8 +4,6 @@ module Zip module IOExtras #:nodoc: CHUNK_SIZE = 131_072 - RANGE_ALL = 0..-1 - class << self def copy_stream(ostream, istream) ostream.write(istream.read(CHUNK_SIZE, +'')) until istream.eof? diff --git a/test/case_sensitivity_test.rb b/test/case_sensitivity_test.rb index 0a9844b1..af4443db 100644 --- a/test/case_sensitivity_test.rb +++ b/test/case_sensitivity_test.rb @@ -5,8 +5,10 @@ class ZipCaseSensitivityTest < MiniTest::Test include CommonZipFileFixture - SRC_FILES = [['test/data/file1.txt', 'testfile.rb'], - ['test/data/file2.txt', 'testFILE.rb']] + SRC_FILES = [ + ['test/data/file1.txt', 'testfile.rb'], + ['test/data/file2.txt', 'testFILE.rb'] + ].freeze def teardown ::Zip.reset! diff --git a/test/entry_set_test.rb b/test/entry_set_test.rb index 4c9f2021..fa91b77d 100644 --- a/test/entry_set_test.rb +++ b/test/entry_set_test.rb @@ -10,7 +10,7 @@ class ZipEntrySetTest < MiniTest::Test ::Zip::Entry.new('zipfile.zip', 'name4', comment: 'comment1'), ::Zip::Entry.new('zipfile.zip', 'name5', comment: 'comment1'), ::Zip::Entry.new('zipfile.zip', 'name6', comment: 'comment1') - ] + ].freeze def setup @zip_entry_set = ::Zip::EntrySet.new(ZIP_ENTRIES) diff --git a/test/extra_field_ut_test.rb b/test/extra_field_ut_test.rb index 74cb21f6..9f16b616 100644 --- a/test/extra_field_ut_test.rb +++ b/test/extra_field_ut_test.rb @@ -11,7 +11,7 @@ class ZipExtraFieldUTTest < MiniTest::Test ["UT\x09\x00\x05PS>APS>A", 0b101, true, false, false], ["UT\x09\x00\x06PS>APS>A", 0b110, false, false, true], ["UT\x13\x00\x07PS>APS>APS>A", 0b111, false, false, false] - ] + ].freeze def test_parse PARSE_TESTS.each do |bin, flags, a, c, m| diff --git a/test/filesystem/dir_iterator_test.rb b/test/filesystem/dir_iterator_test.rb index 6223b44e..bf964c41 100644 --- a/test/filesystem/dir_iterator_test.rb +++ b/test/filesystem/dir_iterator_test.rb @@ -4,7 +4,7 @@ require 'zip/filesystem' class ZipFsDirIteratorTest < MiniTest::Test - FILENAME_ARRAY = %w[f1 f2 f3 f4 f5 f6] + FILENAME_ARRAY = %w[f1 f2 f3 f4 f5 f6].freeze def setup @dir_iter = ::Zip::FileSystem::ZipFsDirIterator.new(FILENAME_ARRAY) diff --git a/test/gentestfiles.rb b/test/gentestfiles.rb index 66818694..5bd82241 100755 --- a/test/gentestfiles.rb +++ b/test/gentestfiles.rb @@ -14,9 +14,11 @@ class TestFiles EMPTY_TEST_DIR = 'test/data/generated/emptytestdir' - ASCII_TEST_FILES = [RANDOM_ASCII_FILE1, RANDOM_ASCII_FILE2, RANDOM_ASCII_FILE3] - BINARY_TEST_FILES = [RANDOM_BINARY_FILE1, RANDOM_BINARY_FILE2] - TEST_DIRECTORIES = [EMPTY_TEST_DIR] + ASCII_TEST_FILES = [ + RANDOM_ASCII_FILE1, RANDOM_ASCII_FILE2, RANDOM_ASCII_FILE3 + ].freeze + BINARY_TEST_FILES = [RANDOM_BINARY_FILE1, RANDOM_BINARY_FILE2].freeze + TEST_DIRECTORIES = [EMPTY_TEST_DIR].freeze TEST_FILES = [ASCII_TEST_FILES, BINARY_TEST_FILES, EMPTY_TEST_DIR].flatten! class << self diff --git a/test/ioextras/abstract_input_stream_test.rb b/test/ioextras/abstract_input_stream_test.rb index 6bc87d4d..04b20990 100644 --- a/test/ioextras/abstract_input_stream_test.rb +++ b/test/ioextras/abstract_input_stream_test.rb @@ -6,10 +6,13 @@ class AbstractInputStreamTest < MiniTest::Test # AbstractInputStream subclass that provides a read method - TEST_LINES = ["Hello world#{$INPUT_RECORD_SEPARATOR}", - "this is the second line#{$INPUT_RECORD_SEPARATOR}", - 'this is the last line'] + TEST_LINES = [ + "Hello world#{$INPUT_RECORD_SEPARATOR}", + "this is the second line#{$INPUT_RECORD_SEPARATOR}", + 'this is the last line' + ].freeze TEST_STRING = TEST_LINES.join + class TestAbstractInputStream include ::Zip::IOExtras::AbstractInputStream @@ -59,7 +62,7 @@ def test_gets_multi_char_seperator 'x' * 48 + "\r\n", 'y' * 49 + "\r\n", 'rest' - ] + ].freeze def test_gets_mulit_char_seperator_split io = TestAbstractInputStream.new(LONG_LINES.join) From e15f80718dfdaf72f63e6a7ede2e3949e9baea8b Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 22 May 2021 18:51:19 +0100 Subject: [PATCH 257/469] Remove stale task from the Rakefile. --- Rakefile | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Rakefile b/Rakefile index 1fc36185..4326fdf9 100644 --- a/Rakefile +++ b/Rakefile @@ -14,10 +14,3 @@ Rake::TestTask.new(:test) do |test| end RuboCop::RakeTask.new - -# Rake::TestTask.new(:zip64_full_test) do |test| -# test.libs << File.join(File.dirname(__FILE__), 'lib') -# test.libs << File.join(File.dirname(__FILE__), 'test') -# test.pattern = File.join(File.dirname(__FILE__), 'test/zip64_full_test.rb') -# test.verbose = true -# end From c438defe73ad204e32f40557ee3e477cebd9c21b Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 23 May 2021 10:15:14 +0100 Subject: [PATCH 258/469] Remove stale .cvsignore file. --- .gitignore | 1 + samples/.cvsignore | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 samples/.cvsignore diff --git a/.gitignore b/.gitignore index f870b5cb..08ebec6b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ Gemfile.lock +samples/*.zip +samples/*.zip.* +samples/zipdialogui.rb coverage pkg/ .ruby-gemset diff --git a/samples/.cvsignore b/samples/.cvsignore deleted file mode 100644 index cf125d08..00000000 --- a/samples/.cvsignore +++ /dev/null @@ -1,4 +0,0 @@ -example.zip -exampleout.zip -filesystem.zip -zipdialogui.rb From 15f3a98ff56b823260c0c6de17091cbf6fd46af4 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 23 May 2021 16:17:13 +0100 Subject: [PATCH 259/469] Fix gentestfiles: create ASCII files with wider char range. --- test/gentestfiles.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/gentestfiles.rb b/test/gentestfiles.rb index 5bd82241..3a537dcd 100755 --- a/test/gentestfiles.rb +++ b/test/gentestfiles.rb @@ -42,7 +42,7 @@ def create_test_files def create_random_ascii(filename, size) File.open(filename, 'wb') do |file| - file << rand while file.tell < size + file << (0...size).map { rand(33..126).chr }.join end end From 667328cc93cd2f4b9b3b5992eac7eff1fb18a713 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 23 May 2021 16:18:16 +0100 Subject: [PATCH 260/469] Fix gentestfiles: create binary files with bytes other than zero. --- test/gentestfiles.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/gentestfiles.rb b/test/gentestfiles.rb index 3a537dcd..4c6e4f09 100755 --- a/test/gentestfiles.rb +++ b/test/gentestfiles.rb @@ -48,7 +48,7 @@ def create_random_ascii(filename, size) def create_random_binary(filename, size) File.open(filename, 'wb') do |file| - file << [rand].pack('V') while file.tell < size + file << (0...size).map { rand(255) }.pack('C*') end end From 3011c4a7051523f7a4eb8aff839582330abefa8e Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 23 May 2021 16:19:19 +0100 Subject: [PATCH 261/469] Fix gentestfiles: remove unused constants. --- test/gentestfiles.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/gentestfiles.rb b/test/gentestfiles.rb index 4c6e4f09..5892fd6c 100755 --- a/test/gentestfiles.rb +++ b/test/gentestfiles.rb @@ -18,8 +18,6 @@ class TestFiles RANDOM_ASCII_FILE1, RANDOM_ASCII_FILE2, RANDOM_ASCII_FILE3 ].freeze BINARY_TEST_FILES = [RANDOM_BINARY_FILE1, RANDOM_BINARY_FILE2].freeze - TEST_DIRECTORIES = [EMPTY_TEST_DIR].freeze - TEST_FILES = [ASCII_TEST_FILES, BINARY_TEST_FILES, EMPTY_TEST_DIR].flatten! class << self def create_test_files From aa3a2cba986ec8ac61d9afcdc04188f167d9e1c8 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 23 May 2021 16:20:10 +0100 Subject: [PATCH 262/469] Fix gentestfiles: fix some overly long lines. --- test/gentestfiles.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/gentestfiles.rb b/test/gentestfiles.rb index 5892fd6c..e5353cae 100755 --- a/test/gentestfiles.rb +++ b/test/gentestfiles.rb @@ -152,7 +152,11 @@ def self.create_test_zips ], 'my zip comment' ) - TEST_ZIP3 = TestZipFile.new('test/data/generated/test1.zip', %w[test/data/file1.txt]) - TEST_ZIP4 = TestZipFile.new('test/data/generated/zipWithDir.zip', ['test/data/file1.txt', - TestFiles::EMPTY_TEST_DIR]) + TEST_ZIP3 = TestZipFile.new( + 'test/data/generated/test1.zip', %w[test/data/file1.txt] + ) + TEST_ZIP4 = TestZipFile.new( + 'test/data/generated/zipWithDir.zip', + ['test/data/file1.txt', TestFiles::EMPTY_TEST_DIR] + ) end From 922afbf5bb300ffc03a1ef9d7907e1c8d6f5e03b Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 25 May 2021 22:04:03 +0100 Subject: [PATCH 263/469] Fix gentestfiles: remove redundant she-bang. --- test/gentestfiles.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/test/gentestfiles.rb b/test/gentestfiles.rb index e5353cae..a95743b3 100755 --- a/test/gentestfiles.rb +++ b/test/gentestfiles.rb @@ -1,4 +1,3 @@ -#!/usr/bin/env ruby # frozen_string_literal: true $VERBOSE = true From dea45613bbcaf3476fa206c478f0a099d87e6aa3 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 13 Sep 2020 18:49:07 +0100 Subject: [PATCH 264/469] Test non-block version of File#get_output_stream. --- test/file_test.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/file_test.rb b/test/file_test.rb index 52fe356e..0b494668 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -73,9 +73,12 @@ def test_get_output_stream assert_equal(count + 1, zf.size) assert_equal('Putting stuff in new_entry.txt', zf.read('new_entry.txt')) - zf.get_output_stream(zf.get_entry('test/data/generated/empty.txt')) do |os| - os.write 'Putting stuff in data/generated/empty.txt' - end + # Use the non-block version of `get_output_stream` not tested elsewhere. + ostream = + zf.get_output_stream(zf.get_entry('test/data/generated/empty.txt')) + ostream.write 'Putting stuff in data/generated/empty.txt' + ostream.close + assert_equal(count + 1, zf.size) assert_equal('Putting stuff in data/generated/empty.txt', zf.read('test/data/generated/empty.txt')) From e70e1d3080efc09fa83963b0b2b08116532ee760 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 26 May 2021 13:35:16 +0100 Subject: [PATCH 265/469] Add `InputStream#size`. This will enable `InputStream` to be used with external APIs that expect to be able to query the expected size of data they will receive, such as S3. Fixes #451. --- lib/zip/input_stream.rb | 7 +++++++ test/input_stream_test.rb | 12 ++++++++++++ 2 files changed, 19 insertions(+) diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index d044a294..3dce2fcc 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -87,6 +87,13 @@ def sysread(length = nil, outbuf = '') @decompressor.read(length, outbuf) end + # Returns the size of the current entry, or `nil` if there isn't one. + def size + return if @current_entry.nil? + + @current_entry.size + end + class << self # Same as #initialize but if a block is passed the opened # stream is passed to the block and closed when the block diff --git a/test/input_stream_test.rb b/test/input_stream_test.rb index 2d192abd..3d0e6217 100644 --- a/test/input_stream_test.rb +++ b/test/input_stream_test.rb @@ -67,6 +67,18 @@ def test_open_io_like_with_block end end + def test_size_no_entry + zis = ::Zip::InputStream.open(TestZipFile::TEST_ZIP2.zip_name) + assert_nil(zis.size) + end + + def test_size_with_entry + ::Zip::InputStream.open(TestZipFile::TEST_ZIP2.zip_name) do |zis| + zis.get_next_entry + assert_equal(123_702, zis.size) + end + end + def test_incomplete_reads ::Zip::InputStream.open(TestZipFile::TEST_ZIP2.zip_name) do |zis| entry = zis.get_next_entry # longAscii.txt From 01acd0488a0b5b67547f34aef150488a6ce6da19 Mon Sep 17 00:00:00 2001 From: Ariel Zelivansky Date: Sun, 23 Sep 2018 12:20:03 +0300 Subject: [PATCH 266/469] Quick fix to prevent crash when mtime is nil --- lib/zip/entry.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 08e10cd0..27e150e1 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -106,7 +106,7 @@ def extra=(field) end def time - if @extra['UniversalTime'] + if @extra['UniversalTime'] && @extra['UniversalTime'].mtime @extra['UniversalTime'].mtime elsif @extra['NTFS'] @extra['NTFS'].mtime From f54e3b7f56dfbda84704782419d1ce18c1ba8ae6 Mon Sep 17 00:00:00 2001 From: Ariel Zelivansky Date: Sun, 23 Sep 2018 13:05:53 +0300 Subject: [PATCH 267/469] Fix improvement & fix NTFS --- lib/zip/entry.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 27e150e1..41a14edc 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -106,9 +106,9 @@ def extra=(field) end def time - if @extra['UniversalTime'] && @extra['UniversalTime'].mtime + if @extra['UniversalTime'] && !@extra['UniversalTime'].mtime.nil? @extra['UniversalTime'].mtime - elsif @extra['NTFS'] + elsif @extra['NTFS'] && !@extra['NTFS'].mtime.nil? @extra['NTFS'].mtime else # Standard time field in central directory has local time From ca51c7ce9bbf8f9341f6d232d337d794274c3a71 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 30 May 2021 11:25:43 +0100 Subject: [PATCH 268/469] Remove Travis config as its community offering is closing. Our GitHub Actions workflows are working well now and integrated with Coveralls, so Travis is no longer needed anyway. --- .travis.yml | 44 -------------------------------------------- README.md | 1 - 2 files changed, 45 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 231e70c8..00000000 --- a/.travis.yml +++ /dev/null @@ -1,44 +0,0 @@ -language: ruby -dist: xenial -cache: bundler -rvm: - - 2.4 - - 2.5 - - 2.6 - - 2.7 - - 3.0 - - ruby-head - - truffleruby-head - - truffleruby -matrix: - fast_finish: true - include: - - rvm: jruby-9.2 - jdk: openjdk8 - - rvm: jruby-9.2 - jdk: openjdk11 - - rvm: jruby-head - jdk: openjdk11 - - rvm: rbx-4 - - name: Rubocop - rvm: 2.4 - script: - - bundle info rubocop - - bundle exec rubocop - allow_failures: - - rvm: ruby-head - - rvm: rbx-4 - - rvm: jruby-head - - rvm: truffleruby-head - - rvm: truffleruby -before_install: - - gem --version -before_script: - - echo `whereis zip` - - echo `whereis unzip` -env: - global: - - JRUBY_OPTS="--debug" - - COVERALLS_PARALLEL=true -notifications: - webhooks: https://coveralls.io/webhook diff --git a/README.md b/README.md index f465ff3b..f5cabcd0 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ [![Gem Version](https://badge.fury.io/rb/rubyzip.svg)](http://badge.fury.io/rb/rubyzip) [![Tests](https://github.com/rubyzip/rubyzip/actions/workflows/tests.yml/badge.svg)](https://github.com/rubyzip/rubyzip/actions/workflows/tests.yml) [![Linter](https://github.com/rubyzip/rubyzip/actions/workflows/lint.yml/badge.svg)](https://github.com/rubyzip/rubyzip/actions/workflows/lint.yml) -[![Build Status](https://secure.travis-ci.org/rubyzip/rubyzip.svg)](http://travis-ci.org/rubyzip/rubyzip) [![Code Climate](https://codeclimate.com/github/rubyzip/rubyzip.svg)](https://codeclimate.com/github/rubyzip/rubyzip) [![Coverage Status](https://img.shields.io/coveralls/rubyzip/rubyzip.svg)](https://coveralls.io/r/rubyzip/rubyzip?branch=master) From 1777a3ff53c70b43dafb2e1076cd970c0c83bc00 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 1 Jun 2021 22:38:43 +0100 Subject: [PATCH 269/469] Make sure `::Zip.force_entry_names_encoding` is reset. It was the one option left out of `::Zip.reset!` for some reason. --- lib/zip.rb | 1 + test/unicode_file_names_and_comments_test.rb | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zip.rb b/lib/zip.rb index 5e142938..9af80b15 100644 --- a/lib/zip.rb +++ b/lib/zip.rb @@ -58,6 +58,7 @@ def reset! @write_zip64_support = false @warn_invalid_date = true @case_insensitive_match = false + @force_entry_names_encoding = nil @validate_entry_sizes = true end diff --git a/test/unicode_file_names_and_comments_test.rb b/test/unicode_file_names_and_comments_test.rb index 1518171a..ceac94fa 100644 --- a/test/unicode_file_names_and_comments_test.rb +++ b/test/unicode_file_names_and_comments_test.rb @@ -47,7 +47,6 @@ def test_unicode_file_name refute_nil(zip.find_entry(filepath)) end end - ::Zip.force_entry_names_encoding = nil ::File.unlink(FILENAME) end From 22a54853e6c1dc39579e6d18a527ab11b200b637 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 1 Jun 2021 22:33:47 +0100 Subject: [PATCH 270/469] Reinstate normalising pathname separators to `/`. But only do it after we have set filename encoding appropriately to avoid breaking multibyte characters with `\`s in them. Fixes #324. --- .rubocop_todo.yml | 2 +- lib/zip/entry.rb | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index fffe19f4..75ed58a3 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -20,7 +20,7 @@ Lint/MissingSuper: # Offense count: 5 # Configuration parameters: CountComments, CountAsOne. Metrics/ClassLength: - Max: 601 + Max: 610 # Offense count: 20 # Configuration parameters: IgnoredMethods. diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 41a14edc..16336df6 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -296,13 +296,12 @@ def read_local_entry(io) #:nodoc:all set_time(@last_mod_date, @last_mod_time) @name = io.read(@name_length) - extra = io.read(@extra_length) - - @name.tr!('\\', '/') if ::Zip.force_entry_names_encoding @name.force_encoding(::Zip.force_entry_names_encoding) end + @name.tr!('\\', '/') # Normalise filepath separators after encoding set. + extra = io.read(@extra_length) if extra && extra.bytesize != @extra_length raise ::Zip::Error, 'Truncated local zip entry header' end @@ -424,10 +423,13 @@ def read_c_dir_entry(io) #:nodoc:all unpack_c_dir_entry(static_sized_fields_buf) check_c_dir_entry_signature set_time(@last_mod_date, @last_mod_time) + @name = io.read(@name_length) if ::Zip.force_entry_names_encoding @name.force_encoding(::Zip.force_entry_names_encoding) end + @name.tr!('\\', '/') # Normalise filepath separators after encoding set. + read_extra_field(io.read(@extra_length)) @comment = io.read(@comment_length) check_c_dir_entry_comment_size From b705085b09861999c602a2fbcf508119cfabd4ea Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 30 May 2021 19:08:31 +0100 Subject: [PATCH 271/469] `Entry#name_safe?` now allows Windows drive mappings. --- lib/zip/entry.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 16336df6..2e484284 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -164,8 +164,9 @@ def name_safe? return false unless cleanpath.relative? root = ::File::SEPARATOR - naive_expanded_path = ::File.join(root, cleanpath.to_s) - ::File.absolute_path(cleanpath.to_s, root) == naive_expanded_path + naive = ::File.join(root, cleanpath.to_s) + # Allow for Windows drive mappings at the root. + ::File.absolute_path(cleanpath.to_s, root).match?(/([A-Z]:)?#{naive}/i) end def local_entry_offset #:nodoc:all From cf22ff1b92c38dd9da1a0f023e5b1567051c6f55 Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Sun, 6 Jun 2021 14:44:52 +0200 Subject: [PATCH 272/469] Preserve eol linefeed for text test data files This prevents converting lf to crlf on Windows. --- test/data/.gitattributes | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 test/data/.gitattributes diff --git a/test/data/.gitattributes b/test/data/.gitattributes new file mode 100644 index 00000000..8c740099 --- /dev/null +++ b/test/data/.gitattributes @@ -0,0 +1,2 @@ +file1.txt eol=lf +file2.txt eol=lf From 0051d5bb1fe2525cdf3a8beaa274aae84d98a1ae Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Sun, 30 May 2021 08:04:56 +0200 Subject: [PATCH 273/469] Read/write test files in binay mode (for Windows compatibility) --- test/file_test.rb | 8 ++++---- test/gentestfiles.rb | 12 ++++++------ test/input_stream_test.rb | 6 +++--- test/output_stream_test.rb | 2 +- test/pass_thru_decompressor_test.rb | 2 +- test/test_helper.rb | 2 +- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/test/file_test.rb b/test/file_test.rb index 0b494668..b34a9474 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -113,21 +113,21 @@ def test_get_output_stream end def test_open_buffer_with_string - string = File.read('test/data/rubycode.zip') + string = File.read('test/data/rubycode.zip', mode: 'rb') ::Zip::File.open_buffer string do |zf| assert zf.entries.map(&:name).include?('zippedruby1.rb') end end def test_open_buffer_with_stringio - string_io = StringIO.new File.read('test/data/rubycode.zip') + string_io = StringIO.new File.read('test/data/rubycode.zip', mode: 'rb') ::Zip::File.open_buffer string_io do |zf| assert zf.entries.map(&:name).include?('zippedruby1.rb') end end def test_close_buffer_with_stringio - string_io = StringIO.new File.read('test/data/rubycode.zip') + string_io = StringIO.new File.read('test/data/rubycode.zip', mode: 'rb') zf = ::Zip::File.open_buffer string_io assert_nil zf.close end @@ -178,7 +178,7 @@ def test_open_buffer_with_io_and_block end def test_open_buffer_without_block - string_io = StringIO.new File.read('test/data/rubycode.zip') + string_io = StringIO.new File.read('test/data/rubycode.zip', mode: 'rb') zf = ::Zip::File.open_buffer string_io assert zf.entries.map(&:name).include?('zippedruby1.rb') end diff --git a/test/gentestfiles.rb b/test/gentestfiles.rb index a95743b3..78709162 100755 --- a/test/gentestfiles.rb +++ b/test/gentestfiles.rb @@ -79,19 +79,19 @@ def self.create_test_zips "zip -q #{TEST_ZIP1.zip_name} -d test/data/file2.txt" ) - File.open('test/data/generated/empty.txt', 'w') {} # Empty file. - File.open('test/data/generated/empty_chmod640.txt', 'w') {} # Empty file. + File.open('test/data/generated/empty.txt', 'wb') {} # Empty file. + File.open('test/data/generated/empty_chmod640.txt', 'wb') {} # Empty file. ::File.chmod(0o640, 'test/data/generated/empty_chmod640.txt') - File.open('test/data/generated/short.txt', 'w') { |file| file << 'ABCDEF' } + File.open('test/data/generated/short.txt', 'wb') { |file| file << 'ABCDEF' } test_text = '' - File.open('test/data/file2.txt') { |file| test_text = file.read } - File.open('test/data/generated/longAscii.txt', 'w') do |file| + File.open('test/data/file2.txt', 'rb') { |file| test_text = file.read } + File.open('test/data/generated/longAscii.txt', 'wb') do |file| file << test_text while file.tell < 1E5 end binary_pattern = '' - File.open('test/data/generated/empty.zip') do |file| + File.open('test/data/generated/empty.zip', 'rb') do |file| binary_pattern = file.read end binary_pattern *= 4 diff --git a/test/input_stream_test.rb b/test/input_stream_test.rb index 3d0e6217..40d19476 100644 --- a/test/input_stream_test.rb +++ b/test/input_stream_test.rb @@ -42,13 +42,13 @@ def test_open_buffer_with_block end def test_open_string_io_without_block - string_io = ::StringIO.new(::File.read(TestZipFile::TEST_ZIP2.zip_name)) + string_io = ::StringIO.new(::File.read(TestZipFile::TEST_ZIP2.zip_name, mode: 'rb')) zis = ::Zip::InputStream.open(string_io) assert_stream_contents(zis, TestZipFile::TEST_ZIP2) end def test_open_string_io_with_block - string_io = ::StringIO.new(::File.read(TestZipFile::TEST_ZIP2.zip_name)) + string_io = ::StringIO.new(::File.read(TestZipFile::TEST_ZIP2.zip_name, mode: 'rb')) ::Zip::InputStream.open(string_io) do |zis| assert_stream_contents(zis, TestZipFile::TEST_ZIP2) assert_equal(true, zis.eof?) @@ -106,7 +106,7 @@ def test_incomplete_reads end def test_incomplete_reads_from_string_io - string_io = ::StringIO.new(::File.read(TestZipFile::TEST_ZIP2.zip_name)) + string_io = ::StringIO.new(::File.read(TestZipFile::TEST_ZIP2.zip_name, mode: 'rb')) ::Zip::InputStream.open(string_io) do |zis| entry = zis.get_next_entry # longAscii.txt assert_equal(false, zis.eof?) diff --git a/test/output_stream_test.rb b/test/output_stream_test.rb index 3b46f615..7a994b36 100644 --- a/test/output_stream_test.rb +++ b/test/output_stream_test.rb @@ -85,7 +85,7 @@ def test_put_next_entry zos << stored_text end - assert(File.read(TEST_ZIP.zip_name)[stored_text]) + assert(File.read(TEST_ZIP.zip_name, mode: 'rb')[stored_text]) ::Zip::File.open(TEST_ZIP.zip_name) do |zf| assert_equal(stored_text, zf.read(entry_name)) end diff --git a/test/pass_thru_decompressor_test.rb b/test/pass_thru_decompressor_test.rb index 937c3ac6..9c251a09 100644 --- a/test/pass_thru_decompressor_test.rb +++ b/test/pass_thru_decompressor_test.rb @@ -6,7 +6,7 @@ class PassThruDecompressorTest < MiniTest::Test def setup super - @file = File.new(TEST_FILE) + @file = File.new(TEST_FILE, 'rb') @decompressor = ::Zip::PassThruDecompressor.new(@file, File.size(TEST_FILE)) end diff --git a/test/test_helper.rb b/test/test_helper.rb index 125ee99c..b51fd5d4 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -57,7 +57,7 @@ module DecompressorTests def setup @ref_text = '' - File.open(TEST_FILE) { |f| @ref_text = f.read } + File.open(TEST_FILE, 'rb') { |f| @ref_text = f.read } @ref_lines = @ref_text.split($INPUT_RECORD_SEPARATOR) end From 5c9b8507293343ca0a44e5321416818199b80b4e Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Thu, 3 Jun 2021 15:56:42 +0200 Subject: [PATCH 274/469] Prevent adding a newline to comments for generated test files on Windows --- test/gentestfiles.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/gentestfiles.rb b/test/gentestfiles.rb index 78709162..50820427 100755 --- a/test/gentestfiles.rb +++ b/test/gentestfiles.rb @@ -108,7 +108,7 @@ def self.create_test_zips if RUBY_PLATFORM.match?(/mswin|mingw|cygwin/) raise "failed to add comment to test zip '#{TEST_ZIP2.zip_name}'" \ unless system( - "echo #{TEST_ZIP2.comment}| zip -zq #{TEST_ZIP2.zip_name}\"" + "cmd /c \" Date: Fri, 4 Jun 2021 15:51:25 +0200 Subject: [PATCH 275/469] Do not hardcode /tmp in entry_test --- test/entry_test.rb | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/test/entry_test.rb b/test/entry_test.rb index 7c1cf60d..d57962ca 100644 --- a/test/entry_test.rb +++ b/test/entry_test.rb @@ -151,32 +151,35 @@ def test_entry_name_cannot_start_with_slash end def test_store_file_without_compression - File.delete('/tmp/no_compress.zip') if File.exist?('/tmp/no_compress.zip') - files = Dir[File.join('test/data/globTest', '**', '**')] + Dir.mktmpdir do |tmp| + tmp_zip = File.join(tmp, 'no_compress.zip') - Zip.setup do |z| - z.write_zip64_support = false - end + Zip.setup do |z| + z.write_zip64_support = false + end - zipfile = Zip::File.open('/tmp/no_compress.zip', Zip::File::CREATE) - mimetype_entry = Zip::Entry.new( - zipfile, # @zipfile - 'mimetype', # @name - compression_method: Zip::Entry::STORED - ) + zipfile = Zip::File.open(tmp_zip, Zip::File::CREATE) + + mimetype_entry = Zip::Entry.new( + zipfile, # @zipfile + 'mimetype', # @name + compression_method: Zip::Entry::STORED + ) + zipfile.add(mimetype_entry, 'test/data/mimetype') - zipfile.add(mimetype_entry, 'test/data/mimetype') + files = Dir[File.join('test/data/globTest', '**', '**')] + files.each do |file| + zipfile.add(file.sub('test/data/globTest/', ''), file) + end - files.each do |file| - zipfile.add(file.sub('test/data/globTest/', ''), file) - end - zipfile.close + zipfile.close - f = File.open('/tmp/no_compress.zip', 'rb') - first_100_bytes = f.read(100) - f.close + f = File.open(tmp_zip, 'rb') + first_100_bytes = f.read(100) + f.close - assert_match(/mimetypeapplication\/epub\+zip/, first_100_bytes) + assert_match(/mimetypeapplication\/epub\+zip/, first_100_bytes) + end end def test_encrypted? From 8a5fef807407c3c945a9ef912efb630c6aa66ccc Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Sat, 5 Jun 2021 10:38:22 +0200 Subject: [PATCH 276/469] Fix FileSystem::ZipFileNameMapper#expand_path on Windows Fixes regression introduced by 0e4dc676a0c447537d233b5738da7ecddc26e12e. --- lib/zip/filesystem.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zip/filesystem.rb b/lib/zip/filesystem.rb index 2815069b..61072700 100644 --- a/lib/zip/filesystem.rb +++ b/lib/zip/filesystem.rb @@ -620,7 +620,7 @@ def each end def expand_path(path) - expanded = ::File.expand_path(path, @pwd) + expanded = path.start_with?('/') ? path.dup : ::File.join(@pwd, path) expanded.gsub!(/\/\.(\/|$)/, '') expanded.gsub!(/[^\/]+\/\.\.(\/|$)/, '') expanded.empty? ? '/' : expanded From cdef4a518738c249c4843c34e9c8bba1c9e99c89 Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Fri, 4 Jun 2021 17:51:09 +0200 Subject: [PATCH 277/469] Prevent directory not empty error when running file_test on Windows Fixed error: ZipFileTest#test_open_buffer_no_op_does_not_change_file: Errno::ENOTEMPTY: Directory not empty @ dir_s_rmdir - D:/a/_temp/d20210605-6612-1yi35sp C:/hostedtoolcache/windows/Ruby/2.4.10/x64/lib/ruby/2.4.0/fileutils.rb:1335:in `rmdir' C:/hostedtoolcache/windows/Ruby/2.4.10/x64/lib/ruby/2.4.0/fileutils.rb:1335:in `block in remove_dir1' C:/hostedtoolcache/windows/Ruby/2.4.10/x64/lib/ruby/2.4.0/fileutils.rb:1349:in `platform_support' C:/hostedtoolcache/windows/Ruby/2.4.10/x64/lib/ruby/2.4.0/fileutils.rb:1334:in `remove_dir1' C:/hostedtoolcache/windows/Ruby/2.4.10/x64/lib/ruby/2.4.0/fileutils.rb:1327:in `remove' C:/hostedtoolcache/windows/Ruby/2.4.10/x64/lib/ruby/2.4.0/fileutils.rb:689:in `block in remove_entry' C:/hostedtoolcache/windows/Ruby/2.4.10/x64/lib/ruby/2.4.0/fileutils.rb:1384:in `ensure in postorder_traverse' C:/hostedtoolcache/windows/Ruby/2.4.10/x64/lib/ruby/2.4.0/fileutils.rb:1384:in `postorder_traverse' C:/hostedtoolcache/windows/Ruby/2.4.10/x64/lib/ruby/2.4.0/fileutils.rb:687:in `remove_entry' C:/hostedtoolcache/windows/Ruby/2.4.10/x64/lib/ruby/2.4.0/tmpdir.rb:101:in `mktmpdir' D:/a/rubyzip/rubyzip/test/file_test.rb:136:in `test_open_buffer_no_op_does_not_change_file' Rationale: File#dup does not behave like what you would expect from #dup on Ruby. File#dup calls dup(2), which has OS dependant behavoir. On Windows, calling File#dup seems to cause an extra reference to an open file, which prevents deleting that file later. With this commit, we leave out the call to File#dup on Windows. It is not clear to me that removing this call has no undesired consequences, but all other existing tests still succeed. --- lib/zip/output_stream.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zip/output_stream.rb b/lib/zip/output_stream.rb index 6bfd9382..5d13ebad 100644 --- a/lib/zip/output_stream.rb +++ b/lib/zip/output_stream.rb @@ -30,7 +30,7 @@ def initialize(file_name, stream = false, encrypter = nil) super() @file_name = file_name @output_stream = if stream - iostream = @file_name.dup + iostream = Zip::RUNNING_ON_WINDOWS ? @file_name : @file_name.dup iostream.reopen(@file_name) iostream.rewind iostream From c3443c06ea47722f96ab140b8354419e7e3775f3 Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Fri, 4 Jun 2021 14:05:59 +0200 Subject: [PATCH 278/469] Make recover file permissions test better understandable --- test/file_test.rb | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/test/file_test.rb b/test/file_test.rb index b34a9474..7ecdac0b 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -286,15 +286,16 @@ def test_add_stored def test_recover_permissions_after_add_files_to_archive src_zip = TEST_ZIP.zip_name - ::File.chmod(0o664, src_zip) - src_file = 'test/data/file2.txt' - entry_name = 'newEntryName.rb' - assert_equal(::File.stat(src_zip).mode, 0o100664) assert(::File.exist?(src_zip)) + + ::File.chmod(0o664, src_zip) + assert_equal(0o100664, ::File.stat(src_zip).mode) + zf = ::Zip::File.new(src_zip, ::Zip::File::CREATE) - zf.add(entry_name, src_file) + zf.add('newEntryName.rb', 'test/data/file2.txt') zf.close - assert_equal(::File.stat(src_zip).mode, 0o100664) + + assert_equal(0o100664, ::File.stat(src_zip).mode) end def test_add_existing_entry_name From 8a24bff1b2ca4fc0f7ca0125ed067fca226503ce Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Fri, 4 Jun 2021 14:18:48 +0200 Subject: [PATCH 279/469] Disable recover file permissions test on Windows --- test/file_test.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/file_test.rb b/test/file_test.rb index 7ecdac0b..a802f105 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -285,6 +285,9 @@ def test_add_stored end def test_recover_permissions_after_add_files_to_archive + # Windows NT does not support granular permissions + skip if Zip::RUNNING_ON_WINDOWS + src_zip = TEST_ZIP.zip_name assert(::File.exist?(src_zip)) From 6ac2cb207d80a0900491538543c5a8589377f8cf Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Fri, 4 Jun 2021 17:50:56 +0200 Subject: [PATCH 280/469] REVERT ME: This disables a test fixed by #486 --- test/file_options_test.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/file_options_test.rb b/test/file_options_test.rb index e9c612ce..4bffa4f1 100644 --- a/test/file_options_test.rb +++ b/test/file_options_test.rb @@ -58,6 +58,10 @@ def test_restore_times_true zip.extract(ENTRY_2, EXTPATH_2) end + # this test is disabled on Windows for now, waiting for #486. + # please remove this after merging #486. + skip if Zip::RUNNING_ON_WINDOWS + assert_time_equal(::File.mtime(TXTPATH), ::File.mtime(EXTPATH_1)) assert_time_equal(::File.mtime(TXTPATH), ::File.mtime(EXTPATH_2)) end From 64c54cc61b654b4cca700c0db5933acfed08d0b2 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 6 Jun 2021 14:56:29 +0100 Subject: [PATCH 281/469] Update Changelog. --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index 293acfb3..14b181ce 100644 --- a/Changelog.md +++ b/Changelog.md @@ -12,6 +12,7 @@ Tooling: +- Fix Windows tests. [#489](https://github.com/rubyzip/rubyzip/pull/489) - Refactor `assert_forwarded` so it does not need `ObjectSpace._id2ref` or `eval`. [#483](https://github.com/rubyzip/rubyzip/pull/483) - Add GitHub Actions CI infrastructure. [#469](https://github.com/rubyzip/rubyzip/issues/469) - Add Ruby 3.0 to CI. [#474](https://github.com/rubyzip/rubyzip/pull/474) From 204d084fdf647f8bfcd26d4757ad01114d93279e Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 2 Jun 2021 12:23:51 +0100 Subject: [PATCH 282/469] Extract `ZipFileNameMapper` from the main filesystem file. --- lib/zip/filesystem.rb | 77 +------------------- lib/zip/filesystem/zip_file_name_mapper.rb | 81 ++++++++++++++++++++++ 2 files changed, 82 insertions(+), 76 deletions(-) create mode 100644 lib/zip/filesystem/zip_file_name_mapper.rb diff --git a/lib/zip/filesystem.rb b/lib/zip/filesystem.rb index 61072700..59bdf145 100644 --- a/lib/zip/filesystem.rb +++ b/lib/zip/filesystem.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'zip' +require_relative 'filesystem/zip_file_name_mapper' module Zip # The ZipFileSystem API provides an API for accessing entries in @@ -556,82 +557,6 @@ def tell @index end end - - # All access to Zip::File from ZipFsFile and ZipFsDir goes through a - # ZipFileNameMapper, which has one responsibility: ensure - class ZipFileNameMapper # :nodoc:all - include Enumerable - - def initialize(zip_file) - @zip_file = zip_file - @pwd = '/' - end - - attr_accessor :pwd - - def find_entry(filename) - @zip_file.find_entry(expand_to_entry(filename)) - end - - def get_entry(filename) - @zip_file.get_entry(expand_to_entry(filename)) - end - - def get_input_stream(filename, &a_proc) - @zip_file.get_input_stream(expand_to_entry(filename), &a_proc) - end - - def get_output_stream(filename, permissions = nil, &a_proc) - @zip_file.get_output_stream( - expand_to_entry(filename), permissions, &a_proc - ) - end - - def glob(pattern, *flags, &block) - @zip_file.glob(expand_to_entry(pattern), *flags, &block) - end - - def read(filename) - @zip_file.read(expand_to_entry(filename)) - end - - def remove(filename) - @zip_file.remove(expand_to_entry(filename)) - end - - def rename(filename, new_name, &continue_on_exists_proc) - @zip_file.rename( - expand_to_entry(filename), - expand_to_entry(new_name), - &continue_on_exists_proc - ) - end - - def mkdir(filename, permissions = 0o755) - @zip_file.mkdir(expand_to_entry(filename), permissions) - end - - # Turns entries into strings and adds leading / - # and removes trailing slash on directories - def each - @zip_file.each do |e| - yield('/' + e.to_s.chomp('/')) - end - end - - def expand_path(path) - expanded = path.start_with?('/') ? path.dup : ::File.join(@pwd, path) - expanded.gsub!(/\/\.(\/|$)/, '') - expanded.gsub!(/[^\/]+\/\.\.(\/|$)/, '') - expanded.empty? ? '/' : expanded - end - - private - - def expand_to_entry(path) - expand_path(path)[1..-1] - end - end end class File diff --git a/lib/zip/filesystem/zip_file_name_mapper.rb b/lib/zip/filesystem/zip_file_name_mapper.rb new file mode 100644 index 00000000..66adffec --- /dev/null +++ b/lib/zip/filesystem/zip_file_name_mapper.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +module Zip + module FileSystem + # All access to Zip::File from ZipFsFile and ZipFsDir goes through a + # ZipFileNameMapper, which has one responsibility: ensure + class ZipFileNameMapper # :nodoc:all + include Enumerable + + def initialize(zip_file) + @zip_file = zip_file + @pwd = '/' + end + + attr_accessor :pwd + + def find_entry(filename) + @zip_file.find_entry(expand_to_entry(filename)) + end + + def get_entry(filename) + @zip_file.get_entry(expand_to_entry(filename)) + end + + def get_input_stream(filename, &a_proc) + @zip_file.get_input_stream(expand_to_entry(filename), &a_proc) + end + + def get_output_stream(filename, permissions = nil, &a_proc) + @zip_file.get_output_stream( + expand_to_entry(filename), permissions, &a_proc + ) + end + + def glob(pattern, *flags, &block) + @zip_file.glob(expand_to_entry(pattern), *flags, &block) + end + + def read(filename) + @zip_file.read(expand_to_entry(filename)) + end + + def remove(filename) + @zip_file.remove(expand_to_entry(filename)) + end + + def rename(filename, new_name, &continue_on_exists_proc) + @zip_file.rename( + expand_to_entry(filename), + expand_to_entry(new_name), + &continue_on_exists_proc + ) + end + + def mkdir(filename, permissions = 0o755) + @zip_file.mkdir(expand_to_entry(filename), permissions) + end + + # Turns entries into strings and adds leading / + # and removes trailing slash on directories + def each + @zip_file.each do |e| + yield("/#{e.to_s.chomp('/')}") + end + end + + def expand_path(path) + expanded = path.start_with?('/') ? path.dup : ::File.join(@pwd, path) + expanded.gsub!(/\/\.(\/|$)/, '') + expanded.gsub!(/[^\/]+\/\.\.(\/|$)/, '') + expanded.empty? ? '/' : expanded + end + + private + + def expand_to_entry(path) + expand_path(path)[1..-1] + end + end + end +end From 239baef8459e7d7ed96d9b8ccdc0e93c1b4c7c19 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 2 Jun 2021 17:04:45 +0100 Subject: [PATCH 283/469] Extract `DirectoryIterator` from the main filesystem file. --- lib/zip/filesystem.rb | 46 +----------------- lib/zip/filesystem/directory_iterator.rb | 48 +++++++++++++++++++ ...tor_test.rb => directory_iterator_test.rb} | 4 +- 3 files changed, 52 insertions(+), 46 deletions(-) create mode 100644 lib/zip/filesystem/directory_iterator.rb rename test/filesystem/{dir_iterator_test.rb => directory_iterator_test.rb} (91%) diff --git a/lib/zip/filesystem.rb b/lib/zip/filesystem.rb index 59bdf145..38d66a96 100644 --- a/lib/zip/filesystem.rb +++ b/lib/zip/filesystem.rb @@ -2,6 +2,7 @@ require 'zip' require_relative 'filesystem/zip_file_name_mapper' +require_relative 'filesystem/directory_iterator' module Zip # The ZipFileSystem API provides an API for accessing entries in @@ -442,7 +443,7 @@ def initialize(mapped_zip) attr_writer :file def new(directory_name) - ZipFsDirIterator.new(entries(directory_name)) + DirectoryIterator.new(entries(directory_name)) end def open(directory_name) @@ -514,49 +515,6 @@ def chroot(*_args) raise NotImplementedError, 'The chroot() function is not implemented' end end - - class ZipFsDirIterator # :nodoc:all - include Enumerable - - def initialize(filenames) - @filenames = filenames - @index = 0 - end - - def close - @filenames = nil - end - - def each(&a_proc) - raise IOError, 'closed directory' if @filenames.nil? - - @filenames.each(&a_proc) - end - - def read - raise IOError, 'closed directory' if @filenames.nil? - - @filenames[(@index += 1) - 1] - end - - def rewind - raise IOError, 'closed directory' if @filenames.nil? - - @index = 0 - end - - def seek(position) - raise IOError, 'closed directory' if @filenames.nil? - - @index = position - end - - def tell - raise IOError, 'closed directory' if @filenames.nil? - - @index - end - end end class File diff --git a/lib/zip/filesystem/directory_iterator.rb b/lib/zip/filesystem/directory_iterator.rb new file mode 100644 index 00000000..91b1fc2e --- /dev/null +++ b/lib/zip/filesystem/directory_iterator.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module Zip + module FileSystem + class DirectoryIterator # :nodoc:all + include Enumerable + + def initialize(filenames) + @filenames = filenames + @index = 0 + end + + def close + @filenames = nil + end + + def each(&a_proc) + raise IOError, 'closed directory' if @filenames.nil? + + @filenames.each(&a_proc) + end + + def read + raise IOError, 'closed directory' if @filenames.nil? + + @filenames[(@index += 1) - 1] + end + + def rewind + raise IOError, 'closed directory' if @filenames.nil? + + @index = 0 + end + + def seek(position) + raise IOError, 'closed directory' if @filenames.nil? + + @index = position + end + + def tell + raise IOError, 'closed directory' if @filenames.nil? + + @index + end + end + end +end diff --git a/test/filesystem/dir_iterator_test.rb b/test/filesystem/directory_iterator_test.rb similarity index 91% rename from test/filesystem/dir_iterator_test.rb rename to test/filesystem/directory_iterator_test.rb index bf964c41..ba809dfd 100644 --- a/test/filesystem/dir_iterator_test.rb +++ b/test/filesystem/directory_iterator_test.rb @@ -3,11 +3,11 @@ require 'test_helper' require 'zip/filesystem' -class ZipFsDirIteratorTest < MiniTest::Test +class DirectoryIteratorTest < MiniTest::Test FILENAME_ARRAY = %w[f1 f2 f3 f4 f5 f6].freeze def setup - @dir_iter = ::Zip::FileSystem::ZipFsDirIterator.new(FILENAME_ARRAY) + @dir_iter = ::Zip::FileSystem::DirectoryIterator.new(FILENAME_ARRAY) end def test_close From a1c9b63e616eb83e5839f443a6539f48ca564695 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 2 Jun 2021 17:22:58 +0100 Subject: [PATCH 284/469] Extract `FileSystem::Dir` from the main filesystem file. --- .rubocop_todo.yml | 1 + lib/zip/filesystem.rb | 92 +------------------ lib/zip/filesystem/dir.rb | 86 +++++++++++++++++ lib/zip/filesystem/zip_file_name_mapper.rb | 4 +- .../{directory_test.rb => dir_test.rb} | 2 +- 5 files changed, 93 insertions(+), 92 deletions(-) create mode 100644 lib/zip/filesystem/dir.rb rename test/filesystem/{directory_test.rb => dir_test.rb} (98%) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 75ed58a3..2931b545 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -77,6 +77,7 @@ Style/IfUnlessModifier: - 'lib/zip/entry.rb' - 'lib/zip/file.rb' - 'lib/zip/filesystem.rb' + - 'lib/zip/filesystem/dir.rb' - 'lib/zip/pass_thru_decompressor.rb' - 'lib/zip/streamable_stream.rb' diff --git a/lib/zip/filesystem.rb b/lib/zip/filesystem.rb index 38d66a96..7a53eb5c 100644 --- a/lib/zip/filesystem.rb +++ b/lib/zip/filesystem.rb @@ -3,6 +3,7 @@ require 'zip' require_relative 'filesystem/zip_file_name_mapper' require_relative 'filesystem/directory_iterator' +require_relative 'filesystem/dir' module Zip # The ZipFileSystem API provides an API for accessing entries in @@ -40,13 +41,13 @@ module Zip module FileSystem def initialize # :nodoc: mapped_zip = ZipFileNameMapper.new(self) - @zip_fs_dir = ZipFsDir.new(mapped_zip) + @zip_fs_dir = Dir.new(mapped_zip) @zip_fs_file = ZipFsFile.new(mapped_zip) @zip_fs_dir.file = @zip_fs_file @zip_fs_file.dir = @zip_fs_dir end - # Returns a ZipFsDir which is much like ruby's builtin Dir (class) + # Returns a FileSystem::Dir which is much like ruby's builtin Dir (class) # object, except it works on the Zip::File on which this method is # invoked def dir @@ -428,93 +429,6 @@ def expand_path(path) @mapped_zip.expand_path(path) end end - - # Instances of this class are normally accessed via the accessor - # ZipFile::dir. An instance of ZipFsDir behaves like ruby's - # builtin Dir (class) object, except it works on ZipFile entries. - # - # The individual methods are not documented due to their - # similarity with the methods in Dir - class ZipFsDir - def initialize(mapped_zip) - @mapped_zip = mapped_zip - end - - attr_writer :file - - def new(directory_name) - DirectoryIterator.new(entries(directory_name)) - end - - def open(directory_name) - dir_iter = new(directory_name) - if block_given? - begin - yield(dir_iter) - return nil - ensure - dir_iter.close - end - end - dir_iter - end - - def pwd - @mapped_zip.pwd - end - alias getwd pwd - - def chdir(directory_name) - unless @file.stat(directory_name).directory? - raise Errno::EINVAL, "Invalid argument - #{directory_name}" - end - - @mapped_zip.pwd = @file.expand_path(directory_name) - end - - def entries(directory_name) - entries = [] - foreach(directory_name) { |e| entries << e } - entries - end - - def glob(*args, &block) - @mapped_zip.glob(*args, &block) - end - - def foreach(directory_name) - unless @file.stat(directory_name).directory? - raise Errno::ENOTDIR, directory_name - end - - path = @file.expand_path(directory_name) - path << '/' unless path.end_with?('/') - path = Regexp.escape(path) - subdir_entry_regex = Regexp.new("^#{path}([^/]+)$") - @mapped_zip.each do |filename| - match = subdir_entry_regex.match(filename) - yield(match[1]) unless match.nil? - end - end - - def delete(entry_name) - unless @file.stat(entry_name).directory? - raise Errno::EINVAL, "Invalid argument - #{entry_name}" - end - - @mapped_zip.remove(entry_name) - end - alias rmdir delete - alias unlink delete - - def mkdir(entry_name, permissions = 0o755) - @mapped_zip.mkdir(entry_name, permissions) - end - - def chroot(*_args) - raise NotImplementedError, 'The chroot() function is not implemented' - end - end end class File diff --git a/lib/zip/filesystem/dir.rb b/lib/zip/filesystem/dir.rb new file mode 100644 index 00000000..a5e1ef9e --- /dev/null +++ b/lib/zip/filesystem/dir.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +module Zip + module FileSystem + class Dir # :nodoc:all + def initialize(mapped_zip) + @mapped_zip = mapped_zip + end + + attr_writer :file + + def new(directory_name) + DirectoryIterator.new(entries(directory_name)) + end + + def open(directory_name) + dir_iter = new(directory_name) + if block_given? + begin + yield(dir_iter) + return nil + ensure + dir_iter.close + end + end + dir_iter + end + + def pwd + @mapped_zip.pwd + end + alias getwd pwd + + def chdir(directory_name) + unless @file.stat(directory_name).directory? + raise Errno::EINVAL, "Invalid argument - #{directory_name}" + end + + @mapped_zip.pwd = @file.expand_path(directory_name) + end + + def entries(directory_name) + entries = [] + foreach(directory_name) { |e| entries << e } + entries + end + + def glob(*args, &block) + @mapped_zip.glob(*args, &block) + end + + def foreach(directory_name) + unless @file.stat(directory_name).directory? + raise Errno::ENOTDIR, directory_name + end + + path = @file.expand_path(directory_name) + path << '/' unless path.end_with?('/') + path = Regexp.escape(path) + subdir_entry_regex = Regexp.new("^#{path}([^/]+)$") + @mapped_zip.each do |filename| + match = subdir_entry_regex.match(filename) + yield(match[1]) unless match.nil? + end + end + + def delete(entry_name) + unless @file.stat(entry_name).directory? + raise Errno::EINVAL, "Invalid argument - #{entry_name}" + end + + @mapped_zip.remove(entry_name) + end + alias rmdir delete + alias unlink delete + + def mkdir(entry_name, permissions = 0o755) + @mapped_zip.mkdir(entry_name, permissions) + end + + def chroot(*_args) + raise NotImplementedError, 'The chroot() function is not implemented' + end + end + end +end diff --git a/lib/zip/filesystem/zip_file_name_mapper.rb b/lib/zip/filesystem/zip_file_name_mapper.rb index 66adffec..fda5660d 100644 --- a/lib/zip/filesystem/zip_file_name_mapper.rb +++ b/lib/zip/filesystem/zip_file_name_mapper.rb @@ -2,8 +2,8 @@ module Zip module FileSystem - # All access to Zip::File from ZipFsFile and ZipFsDir goes through a - # ZipFileNameMapper, which has one responsibility: ensure + # All access to Zip::File from ZipFsFile and FileSystem::Dir goes through + # a ZipFileNameMapper, which has one responsibility: ensure class ZipFileNameMapper # :nodoc:all include Enumerable diff --git a/test/filesystem/directory_test.rb b/test/filesystem/dir_test.rb similarity index 98% rename from test/filesystem/directory_test.rb rename to test/filesystem/dir_test.rb index 57177028..2db69c22 100644 --- a/test/filesystem/directory_test.rb +++ b/test/filesystem/dir_test.rb @@ -3,7 +3,7 @@ require 'test_helper' require 'zip/filesystem' -class ZipFsDirectoryTest < MiniTest::Test +class DirectoryTest < MiniTest::Test TEST_ZIP = 'test/data/generated/zipWithDirs_copy.zip' GLOB_TEST_ZIP = 'test/data/globTest.zip' From d1329299c3db7deb1542e0035fa9f3283860599d Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 2 Jun 2021 19:42:18 +0100 Subject: [PATCH 285/469] Extract `FileSystem::File` from the main filesystem file. --- .rubocop_todo.yml | 6 +- lib/zip/filesystem.rb | 376 +-------------------- lib/zip/filesystem/file.rb | 374 ++++++++++++++++++++ lib/zip/filesystem/zip_file_name_mapper.rb | 4 +- test/filesystem/file_mutating_test.rb | 2 +- test/filesystem/file_nonmutating_test.rb | 2 +- test/filesystem/file_stat_test.rb | 2 +- 7 files changed, 386 insertions(+), 380 deletions(-) create mode 100644 lib/zip/filesystem/file.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 2931b545..68f251b9 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -47,7 +47,7 @@ Metrics/PerceivedComplexity: Naming/AccessorMethodName: Exclude: - 'lib/zip/entry.rb' - - 'lib/zip/filesystem.rb' + - 'lib/zip/filesystem/file.rb' - 'lib/zip/input_stream.rb' - 'lib/zip/streamable_stream.rb' @@ -76,8 +76,8 @@ Style/IfUnlessModifier: Exclude: - 'lib/zip/entry.rb' - 'lib/zip/file.rb' - - 'lib/zip/filesystem.rb' - 'lib/zip/filesystem/dir.rb' + - 'lib/zip/filesystem/file.rb' - 'lib/zip/pass_thru_decompressor.rb' - 'lib/zip/streamable_stream.rb' @@ -101,7 +101,7 @@ Style/NumericPredicate: - 'lib/zip/extra_field/universal_time.rb' - 'lib/zip/extra_field/unix.rb' - 'lib/zip/file.rb' - - 'lib/zip/filesystem.rb' + - 'lib/zip/filesystem/file.rb' - 'lib/zip/input_stream.rb' - 'lib/zip/ioextras.rb' - 'lib/zip/ioextras/abstract_input_stream.rb' diff --git a/lib/zip/filesystem.rb b/lib/zip/filesystem.rb index 7a53eb5c..5fe0c994 100644 --- a/lib/zip/filesystem.rb +++ b/lib/zip/filesystem.rb @@ -4,6 +4,7 @@ require_relative 'filesystem/zip_file_name_mapper' require_relative 'filesystem/directory_iterator' require_relative 'filesystem/dir' +require_relative 'filesystem/file' module Zip # The ZipFileSystem API provides an API for accessing entries in @@ -42,7 +43,7 @@ module FileSystem def initialize # :nodoc: mapped_zip = ZipFileNameMapper.new(self) @zip_fs_dir = Dir.new(mapped_zip) - @zip_fs_file = ZipFsFile.new(mapped_zip) + @zip_fs_file = File.new(mapped_zip) @zip_fs_dir.file = @zip_fs_file @zip_fs_file.dir = @zip_fs_dir end @@ -54,381 +55,12 @@ def dir @zip_fs_dir end - # Returns a ZipFsFile which is much like ruby's builtin File (class) - # object, except it works on the Zip::File on which this method is + # Returns a FileSystem::File which is much like ruby's builtin File + # (class) object, except it works on the Zip::File on which this method is # invoked def file @zip_fs_file end - - # Instances of this class are normally accessed via the accessor - # Zip::File::file. An instance of ZipFsFile behaves like ruby's - # builtin File (class) object, except it works on Zip::File entries. - # - # The individual methods are not documented due to their - # similarity with the methods in File - class ZipFsFile - attr_writer :dir - - class ZipFsStat - class << self - def delegate_to_fs_file(*methods) - methods.each do |method| - class_eval <<-END_EVAL, __FILE__, __LINE__ + 1 - def #{method} # def file? - @zip_fs_file.#{method}(@entry_name) # @zip_fs_file.file?(@entry_name) - end # end - END_EVAL - end - end - end - - def initialize(zip_fs_file, entry_name) - @zip_fs_file = zip_fs_file - @entry_name = entry_name - end - - def kind_of?(type) - super || type == ::File::Stat - end - - delegate_to_fs_file :file?, :directory?, :pipe?, :chardev?, :symlink?, - :socket?, :blockdev?, :readable?, :readable_real?, :writable?, :ctime, - :writable_real?, :executable?, :executable_real?, :sticky?, :owned?, - :grpowned?, :setuid?, :setgid?, :zero?, :size, :size?, :mtime, :atime - - def blocks - nil - end - - def get_entry - @zip_fs_file.__send__(:get_entry, @entry_name) - end - private :get_entry - - def gid - e = get_entry - if e.extra.member? 'IUnix' - e.extra['IUnix'].gid || 0 - else - 0 - end - end - - def uid - e = get_entry - if e.extra.member? 'IUnix' - e.extra['IUnix'].uid || 0 - else - 0 - end - end - - def ino - 0 - end - - def dev - 0 - end - - def rdev - 0 - end - - def rdev_major - 0 - end - - def rdev_minor - 0 - end - - def ftype - if file? - 'file' - elsif directory? - 'directory' - else - raise StandardError, 'Unknown file type' - end - end - - def nlink - 1 - end - - def blksize - nil - end - - def mode - e = get_entry - if e.fstype == 3 - e.external_file_attributes >> 16 - else - 33_206 # 33206 is equivalent to -rw-rw-rw- - end - end - end - - def initialize(mapped_zip) - @mapped_zip = mapped_zip - end - - def get_entry(filename) - unless exists?(filename) - raise Errno::ENOENT, "No such file or directory - #{filename}" - end - - @mapped_zip.find_entry(filename) - end - private :get_entry - - def unix_mode_cmp(filename, mode) - e = get_entry(filename) - e.fstype == 3 && ((e.external_file_attributes >> 16) & mode) != 0 - rescue Errno::ENOENT - false - end - private :unix_mode_cmp - - def exists?(filename) - expand_path(filename) == '/' || !@mapped_zip.find_entry(filename).nil? - end - alias exist? exists? - - # Permissions not implemented, so if the file exists it is accessible - alias owned? exists? - alias grpowned? exists? - - def readable?(filename) - unix_mode_cmp(filename, 0o444) - end - alias readable_real? readable? - - def writable?(filename) - unix_mode_cmp(filename, 0o222) - end - alias writable_real? writable? - - def executable?(filename) - unix_mode_cmp(filename, 0o111) - end - alias executable_real? executable? - - def setuid?(filename) - unix_mode_cmp(filename, 0o4000) - end - - def setgid?(filename) - unix_mode_cmp(filename, 0o2000) - end - - def sticky?(filename) - unix_mode_cmp(filename, 0o1000) - end - - def umask(*args) - ::File.umask(*args) - end - - def truncate(_filename, _len) - raise StandardError, 'truncate not supported' - end - - def directory?(filename) - entry = @mapped_zip.find_entry(filename) - expand_path(filename) == '/' || (!entry.nil? && entry.directory?) - end - - def open(filename, mode = 'r', permissions = 0o644, &block) - mode = mode.tr('b', '') # ignore b option - case mode - when 'r' - @mapped_zip.get_input_stream(filename, &block) - when 'w' - @mapped_zip.get_output_stream(filename, permissions, &block) - else - raise StandardError, "openmode '#{mode} not supported" unless mode == 'r' - end - end - - def new(filename, mode = 'r') - self.open(filename, mode) - end - - def size(filename) - @mapped_zip.get_entry(filename).size - end - - # Returns nil for not found and nil for directories - def size?(filename) - entry = @mapped_zip.find_entry(filename) - entry.nil? || entry.directory? ? nil : entry.size - end - - def chown(owner, group, *filenames) - filenames.each do |filename| - e = get_entry(filename) - e.extra.create('IUnix') unless e.extra.member?('IUnix') - e.extra['IUnix'].uid = owner - e.extra['IUnix'].gid = group - end - filenames.size - end - - def chmod(mode, *filenames) - filenames.each do |filename| - e = get_entry(filename) - e.fstype = 3 # force convertion filesystem type to unix - e.unix_perms = mode - e.external_file_attributes = mode << 16 - e.dirty = true - end - filenames.size - end - - def zero?(filename) - sz = size(filename) - sz.nil? || sz == 0 - rescue Errno::ENOENT - false - end - - def file?(filename) - entry = @mapped_zip.find_entry(filename) - !entry.nil? && entry.file? - end - - def dirname(filename) - ::File.dirname(filename) - end - - def basename(filename) - ::File.basename(filename) - end - - def split(filename) - ::File.split(filename) - end - - def join(*fragments) - ::File.join(*fragments) - end - - def utime(modified_time, *filenames) - filenames.each do |filename| - get_entry(filename).time = modified_time - end - end - - def mtime(filename) - @mapped_zip.get_entry(filename).mtime - end - - def atime(filename) - e = get_entry(filename) - if e.extra.member? 'UniversalTime' - e.extra['UniversalTime'].atime - elsif e.extra.member? 'NTFS' - e.extra['NTFS'].atime - end - end - - def ctime(filename) - e = get_entry(filename) - if e.extra.member? 'UniversalTime' - e.extra['UniversalTime'].ctime - elsif e.extra.member? 'NTFS' - e.extra['NTFS'].ctime - end - end - - def pipe?(_filename) - false - end - - def blockdev?(_filename) - false - end - - def chardev?(_filename) - false - end - - def symlink?(_filename) - false - end - - def socket?(_filename) - false - end - - def ftype(filename) - @mapped_zip.get_entry(filename).directory? ? 'directory' : 'file' - end - - def readlink(_filename) - raise NotImplementedError, 'The readlink() function is not implemented' - end - - def symlink(_filename, _symlink_name) - raise NotImplementedError, 'The symlink() function is not implemented' - end - - def link(_filename, _symlink_name) - raise NotImplementedError, 'The link() function is not implemented' - end - - def pipe - raise NotImplementedError, 'The pipe() function is not implemented' - end - - def stat(filename) - raise Errno::ENOENT, filename unless exists?(filename) - - ZipFsStat.new(self, filename) - end - - alias lstat stat - - def readlines(filename) - self.open(filename, &:readlines) - end - - def read(filename) - @mapped_zip.read(filename) - end - - def popen(*args, &a_proc) - ::File.popen(*args, &a_proc) - end - - def foreach(filename, sep = $INPUT_RECORD_SEPARATOR, &a_proc) - self.open(filename) { |is| is.each_line(sep, &a_proc) } - end - - def delete(*args) - args.each do |filename| - if directory?(filename) - raise Errno::EISDIR, "Is a directory - \"#{filename}\"" - end - - @mapped_zip.remove(filename) - end - end - - def rename(file_to_rename, new_name) - @mapped_zip.rename(file_to_rename, new_name) { true } - end - - alias unlink delete - - def expand_path(path) - @mapped_zip.expand_path(path) - end - end end class File diff --git a/lib/zip/filesystem/file.rb b/lib/zip/filesystem/file.rb new file mode 100644 index 00000000..4c66209b --- /dev/null +++ b/lib/zip/filesystem/file.rb @@ -0,0 +1,374 @@ +# frozen_string_literal: true + +module Zip + module FileSystem + # Instances of this class are normally accessed via the accessor + # Zip::File::file. An instance of File behaves like ruby's + # builtin File (class) object, except it works on Zip::File entries. + # + # The individual methods are not documented due to their + # similarity with the methods in File + class File # :nodoc:all + attr_writer :dir + + class ZipFsStat + class << self + def delegate_to_fs_file(*methods) + methods.each do |method| + class_eval <<-END_EVAL, __FILE__, __LINE__ + 1 + def #{method} # def file? + @zip_fs_file.#{method}(@entry_name) # @zip_fs_file.file?(@entry_name) + end # end + END_EVAL + end + end + end + + def initialize(zip_fs_file, entry_name) + @zip_fs_file = zip_fs_file + @entry_name = entry_name + end + + def kind_of?(type) + super || type == ::File::Stat + end + + delegate_to_fs_file :file?, :directory?, :pipe?, :chardev?, :symlink?, + :socket?, :blockdev?, :readable?, :readable_real?, :writable?, :ctime, + :writable_real?, :executable?, :executable_real?, :sticky?, :owned?, + :grpowned?, :setuid?, :setgid?, :zero?, :size, :size?, :mtime, :atime + + def blocks + nil + end + + def get_entry + @zip_fs_file.__send__(:get_entry, @entry_name) + end + private :get_entry + + def gid + e = get_entry + if e.extra.member? 'IUnix' + e.extra['IUnix'].gid || 0 + else + 0 + end + end + + def uid + e = get_entry + if e.extra.member? 'IUnix' + e.extra['IUnix'].uid || 0 + else + 0 + end + end + + def ino + 0 + end + + def dev + 0 + end + + def rdev + 0 + end + + def rdev_major + 0 + end + + def rdev_minor + 0 + end + + def ftype + if file? + 'file' + elsif directory? + 'directory' + else + raise StandardError, 'Unknown file type' + end + end + + def nlink + 1 + end + + def blksize + nil + end + + def mode + e = get_entry + if e.fstype == 3 + e.external_file_attributes >> 16 + else + 33_206 # 33206 is equivalent to -rw-rw-rw- + end + end + end + + def initialize(mapped_zip) + @mapped_zip = mapped_zip + end + + def get_entry(filename) + unless exists?(filename) + raise Errno::ENOENT, "No such file or directory - #{filename}" + end + + @mapped_zip.find_entry(filename) + end + private :get_entry + + def unix_mode_cmp(filename, mode) + e = get_entry(filename) + e.fstype == 3 && ((e.external_file_attributes >> 16) & mode) != 0 + rescue Errno::ENOENT + false + end + private :unix_mode_cmp + + def exists?(filename) + expand_path(filename) == '/' || !@mapped_zip.find_entry(filename).nil? + end + alias exist? exists? + + # Permissions not implemented, so if the file exists it is accessible + alias owned? exists? + alias grpowned? exists? + + def readable?(filename) + unix_mode_cmp(filename, 0o444) + end + alias readable_real? readable? + + def writable?(filename) + unix_mode_cmp(filename, 0o222) + end + alias writable_real? writable? + + def executable?(filename) + unix_mode_cmp(filename, 0o111) + end + alias executable_real? executable? + + def setuid?(filename) + unix_mode_cmp(filename, 0o4000) + end + + def setgid?(filename) + unix_mode_cmp(filename, 0o2000) + end + + def sticky?(filename) + unix_mode_cmp(filename, 0o1000) + end + + def umask(*args) + ::File.umask(*args) + end + + def truncate(_filename, _len) + raise StandardError, 'truncate not supported' + end + + def directory?(filename) + entry = @mapped_zip.find_entry(filename) + expand_path(filename) == '/' || (!entry.nil? && entry.directory?) + end + + def open(filename, mode = 'r', permissions = 0o644, &block) + mode = mode.tr('b', '') # ignore b option + case mode + when 'r' + @mapped_zip.get_input_stream(filename, &block) + when 'w' + @mapped_zip.get_output_stream(filename, permissions, &block) + else + raise StandardError, "openmode '#{mode} not supported" unless mode == 'r' + end + end + + def new(filename, mode = 'r') + self.open(filename, mode) + end + + def size(filename) + @mapped_zip.get_entry(filename).size + end + + # Returns nil for not found and nil for directories + def size?(filename) + entry = @mapped_zip.find_entry(filename) + entry.nil? || entry.directory? ? nil : entry.size + end + + def chown(owner, group, *filenames) + filenames.each do |filename| + e = get_entry(filename) + e.extra.create('IUnix') unless e.extra.member?('IUnix') + e.extra['IUnix'].uid = owner + e.extra['IUnix'].gid = group + end + filenames.size + end + + def chmod(mode, *filenames) + filenames.each do |filename| + e = get_entry(filename) + e.fstype = 3 # force convertion filesystem type to unix + e.unix_perms = mode + e.external_file_attributes = mode << 16 + e.dirty = true + end + filenames.size + end + + def zero?(filename) + sz = size(filename) + sz.nil? || sz == 0 + rescue Errno::ENOENT + false + end + + def file?(filename) + entry = @mapped_zip.find_entry(filename) + !entry.nil? && entry.file? + end + + def dirname(filename) + ::File.dirname(filename) + end + + def basename(filename) + ::File.basename(filename) + end + + def split(filename) + ::File.split(filename) + end + + def join(*fragments) + ::File.join(*fragments) + end + + def utime(modified_time, *filenames) + filenames.each do |filename| + get_entry(filename).time = modified_time + end + end + + def mtime(filename) + @mapped_zip.get_entry(filename).mtime + end + + def atime(filename) + e = get_entry(filename) + if e.extra.member? 'UniversalTime' + e.extra['UniversalTime'].atime + elsif e.extra.member? 'NTFS' + e.extra['NTFS'].atime + end + end + + def ctime(filename) + e = get_entry(filename) + if e.extra.member? 'UniversalTime' + e.extra['UniversalTime'].ctime + elsif e.extra.member? 'NTFS' + e.extra['NTFS'].ctime + end + end + + def pipe?(_filename) + false + end + + def blockdev?(_filename) + false + end + + def chardev?(_filename) + false + end + + def symlink?(_filename) + false + end + + def socket?(_filename) + false + end + + def ftype(filename) + @mapped_zip.get_entry(filename).directory? ? 'directory' : 'file' + end + + def readlink(_filename) + raise NotImplementedError, 'The readlink() function is not implemented' + end + + def symlink(_filename, _symlink_name) + raise NotImplementedError, 'The symlink() function is not implemented' + end + + def link(_filename, _symlink_name) + raise NotImplementedError, 'The link() function is not implemented' + end + + def pipe + raise NotImplementedError, 'The pipe() function is not implemented' + end + + def stat(filename) + raise Errno::ENOENT, filename unless exists?(filename) + + ZipFsStat.new(self, filename) + end + + alias lstat stat + + def readlines(filename) + self.open(filename, &:readlines) + end + + def read(filename) + @mapped_zip.read(filename) + end + + def popen(*args, &a_proc) + ::File.popen(*args, &a_proc) + end + + def foreach(filename, sep = $INPUT_RECORD_SEPARATOR, &a_proc) + self.open(filename) { |is| is.each_line(sep, &a_proc) } + end + + def delete(*args) + args.each do |filename| + if directory?(filename) + raise Errno::EISDIR, "Is a directory - \"#{filename}\"" + end + + @mapped_zip.remove(filename) + end + end + + def rename(file_to_rename, new_name) + @mapped_zip.rename(file_to_rename, new_name) { true } + end + + alias unlink delete + + def expand_path(path) + @mapped_zip.expand_path(path) + end + end + end +end diff --git a/lib/zip/filesystem/zip_file_name_mapper.rb b/lib/zip/filesystem/zip_file_name_mapper.rb index fda5660d..aa699011 100644 --- a/lib/zip/filesystem/zip_file_name_mapper.rb +++ b/lib/zip/filesystem/zip_file_name_mapper.rb @@ -2,8 +2,8 @@ module Zip module FileSystem - # All access to Zip::File from ZipFsFile and FileSystem::Dir goes through - # a ZipFileNameMapper, which has one responsibility: ensure + # All access to Zip::File from FileSystem::File and FileSystem::Dir + # goes through a ZipFileNameMapper, which has one responsibility: ensure class ZipFileNameMapper # :nodoc:all include Enumerable diff --git a/test/filesystem/file_mutating_test.rb b/test/filesystem/file_mutating_test.rb index 91d45afb..6264f0f0 100644 --- a/test/filesystem/file_mutating_test.rb +++ b/test/filesystem/file_mutating_test.rb @@ -3,7 +3,7 @@ require 'test_helper' require 'zip/filesystem' -class ZipFsFileMutatingTest < MiniTest::Test +class FileMutatingTest < MiniTest::Test TEST_ZIP = 'test/data/generated/zipWithDirs_copy.zip' def setup FileUtils.cp('test/data/zipWithDirs.zip', TEST_ZIP) diff --git a/test/filesystem/file_nonmutating_test.rb b/test/filesystem/file_nonmutating_test.rb index bc0e41d3..d4007e6f 100644 --- a/test/filesystem/file_nonmutating_test.rb +++ b/test/filesystem/file_nonmutating_test.rb @@ -3,7 +3,7 @@ require 'test_helper' require 'zip/filesystem' -class ZipFsFileNonmutatingTest < MiniTest::Test +class FileNonmutatingTest < MiniTest::Test def setup @zipsha = Digest::SHA1.file('test/data/zipWithDirs.zip') @zip_file = ::Zip::File.new('test/data/zipWithDirs.zip') diff --git a/test/filesystem/file_stat_test.rb b/test/filesystem/file_stat_test.rb index 0f6e2b50..f10a5db4 100644 --- a/test/filesystem/file_stat_test.rb +++ b/test/filesystem/file_stat_test.rb @@ -3,7 +3,7 @@ require 'test_helper' require 'zip/filesystem' -class ZipFsFileStatTest < MiniTest::Test +class FileStatTest < MiniTest::Test def setup @zip_file = ::Zip::File.new('test/data/zipWithDirs.zip') end From 7b2e9c7970de6f34f23cc282766cfda734865eed Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 2 Jun 2021 21:37:20 +0100 Subject: [PATCH 286/469] Extract `FileSystem::File::Stat` from `FileSystem::File`. --- .rubocop_todo.yml | 2 +- lib/zip/filesystem/file.rb | 106 +------------------------------ lib/zip/filesystem/file_stat.rb | 109 ++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 104 deletions(-) create mode 100644 lib/zip/filesystem/file_stat.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 68f251b9..a7095755 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -47,7 +47,7 @@ Metrics/PerceivedComplexity: Naming/AccessorMethodName: Exclude: - 'lib/zip/entry.rb' - - 'lib/zip/filesystem/file.rb' + - 'lib/zip/filesystem/file_stat.rb' - 'lib/zip/input_stream.rb' - 'lib/zip/streamable_stream.rb' diff --git a/lib/zip/filesystem/file.rb b/lib/zip/filesystem/file.rb index 4c66209b..9b47161a 100644 --- a/lib/zip/filesystem/file.rb +++ b/lib/zip/filesystem/file.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require_relative 'file_stat' + module Zip module FileSystem # Instances of this class are normally accessed via the accessor @@ -11,108 +13,6 @@ module FileSystem class File # :nodoc:all attr_writer :dir - class ZipFsStat - class << self - def delegate_to_fs_file(*methods) - methods.each do |method| - class_eval <<-END_EVAL, __FILE__, __LINE__ + 1 - def #{method} # def file? - @zip_fs_file.#{method}(@entry_name) # @zip_fs_file.file?(@entry_name) - end # end - END_EVAL - end - end - end - - def initialize(zip_fs_file, entry_name) - @zip_fs_file = zip_fs_file - @entry_name = entry_name - end - - def kind_of?(type) - super || type == ::File::Stat - end - - delegate_to_fs_file :file?, :directory?, :pipe?, :chardev?, :symlink?, - :socket?, :blockdev?, :readable?, :readable_real?, :writable?, :ctime, - :writable_real?, :executable?, :executable_real?, :sticky?, :owned?, - :grpowned?, :setuid?, :setgid?, :zero?, :size, :size?, :mtime, :atime - - def blocks - nil - end - - def get_entry - @zip_fs_file.__send__(:get_entry, @entry_name) - end - private :get_entry - - def gid - e = get_entry - if e.extra.member? 'IUnix' - e.extra['IUnix'].gid || 0 - else - 0 - end - end - - def uid - e = get_entry - if e.extra.member? 'IUnix' - e.extra['IUnix'].uid || 0 - else - 0 - end - end - - def ino - 0 - end - - def dev - 0 - end - - def rdev - 0 - end - - def rdev_major - 0 - end - - def rdev_minor - 0 - end - - def ftype - if file? - 'file' - elsif directory? - 'directory' - else - raise StandardError, 'Unknown file type' - end - end - - def nlink - 1 - end - - def blksize - nil - end - - def mode - e = get_entry - if e.fstype == 3 - e.external_file_attributes >> 16 - else - 33_206 # 33206 is equivalent to -rw-rw-rw- - end - end - end - def initialize(mapped_zip) @mapped_zip = mapped_zip end @@ -329,7 +229,7 @@ def pipe def stat(filename) raise Errno::ENOENT, filename unless exists?(filename) - ZipFsStat.new(self, filename) + Stat.new(self, filename) end alias lstat stat diff --git a/lib/zip/filesystem/file_stat.rb b/lib/zip/filesystem/file_stat.rb new file mode 100644 index 00000000..da53bb26 --- /dev/null +++ b/lib/zip/filesystem/file_stat.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +module Zip + module FileSystem + class File # :nodoc:all + class Stat # :nodoc:all + class << self + def delegate_to_fs_file(*methods) + methods.each do |method| + class_eval <<-END_EVAL, __FILE__, __LINE__ + 1 + def #{method} # def file? + @zip_fs_file.#{method}(@entry_name) # @zip_fs_file.file?(@entry_name) + end # end + END_EVAL + end + end + end + + def initialize(zip_fs_file, entry_name) + @zip_fs_file = zip_fs_file + @entry_name = entry_name + end + + def kind_of?(type) + super || type == ::File::Stat + end + + delegate_to_fs_file :file?, :directory?, :pipe?, :chardev?, :symlink?, + :socket?, :blockdev?, :readable?, :readable_real?, :writable?, :ctime, + :writable_real?, :executable?, :executable_real?, :sticky?, :owned?, + :grpowned?, :setuid?, :setgid?, :zero?, :size, :size?, :mtime, :atime + + def blocks + nil + end + + def get_entry + @zip_fs_file.__send__(:get_entry, @entry_name) + end + private :get_entry + + def gid + e = get_entry + if e.extra.member? 'IUnix' + e.extra['IUnix'].gid || 0 + else + 0 + end + end + + def uid + e = get_entry + if e.extra.member? 'IUnix' + e.extra['IUnix'].uid || 0 + else + 0 + end + end + + def ino + 0 + end + + def dev + 0 + end + + def rdev + 0 + end + + def rdev_major + 0 + end + + def rdev_minor + 0 + end + + def ftype + if file? + 'file' + elsif directory? + 'directory' + else + raise StandardError, 'Unknown file type' + end + end + + def nlink + 1 + end + + def blksize + nil + end + + def mode + e = get_entry + if e.fstype == 3 + e.external_file_attributes >> 16 + else + 33_206 # 33206 is equivalent to -rw-rw-rw- + end + end + end + end + end +end From 99ecf3638f86d702b65b292530e2faad0fecabec Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 2 Jun 2021 22:13:23 +0100 Subject: [PATCH 287/469] Remove spurious empty line at start of module. --- lib/zip/filesystem.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/zip/filesystem.rb b/lib/zip/filesystem.rb index 5fe0c994..26895df5 100644 --- a/lib/zip/filesystem.rb +++ b/lib/zip/filesystem.rb @@ -38,7 +38,6 @@ module Zip # |zipfile| # puts zipfile.file.read("first.txt") # } - module FileSystem def initialize # :nodoc: mapped_zip = ZipFileNameMapper.new(self) From 64a162ced4eb03e9472a1b03341146d769e341a8 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 2 Jun 2021 22:14:19 +0100 Subject: [PATCH 288/469] Refactor `FileSystem::File::Stat.delegate_to_fs_file`. Now uses `class_exec` instead of `class_eval`. --- lib/zip/filesystem/file_stat.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/zip/filesystem/file_stat.rb b/lib/zip/filesystem/file_stat.rb index da53bb26..6c43699d 100644 --- a/lib/zip/filesystem/file_stat.rb +++ b/lib/zip/filesystem/file_stat.rb @@ -7,11 +7,11 @@ class Stat # :nodoc:all class << self def delegate_to_fs_file(*methods) methods.each do |method| - class_eval <<-END_EVAL, __FILE__, __LINE__ + 1 - def #{method} # def file? - @zip_fs_file.#{method}(@entry_name) # @zip_fs_file.file?(@entry_name) - end # end - END_EVAL + class_exec do + define_method(method) do + @zip_fs_file.__send__(method, @entry_name) + end + end end end end From 9d8fc05c439b75adfa6e2889cde6bea04bf738be Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 2 Jun 2021 22:28:40 +0100 Subject: [PATCH 289/469] Refactor `get_entry` in `FileSystem::File(::Stat)`. Rename it to `find_entry` because that is ultimately what is called on the underlying zip file. Make `FileSystem::File#find_entry` public as it need to be called from `FileSystem::File::Stat`, so now we can avoid `__send__`. Neither class is documented anyway, so no harm done there. --- .rubocop_todo.yml | 1 - lib/zip/filesystem/file.rb | 15 +++++++-------- lib/zip/filesystem/file_stat.rb | 17 +++++++++-------- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index a7095755..aafa7fe7 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -47,7 +47,6 @@ Metrics/PerceivedComplexity: Naming/AccessorMethodName: Exclude: - 'lib/zip/entry.rb' - - 'lib/zip/filesystem/file_stat.rb' - 'lib/zip/input_stream.rb' - 'lib/zip/streamable_stream.rb' diff --git a/lib/zip/filesystem/file.rb b/lib/zip/filesystem/file.rb index 9b47161a..76adad14 100644 --- a/lib/zip/filesystem/file.rb +++ b/lib/zip/filesystem/file.rb @@ -17,17 +17,16 @@ def initialize(mapped_zip) @mapped_zip = mapped_zip end - def get_entry(filename) + def find_entry(filename) unless exists?(filename) raise Errno::ENOENT, "No such file or directory - #{filename}" end @mapped_zip.find_entry(filename) end - private :get_entry def unix_mode_cmp(filename, mode) - e = get_entry(filename) + e = find_entry(filename) e.fstype == 3 && ((e.external_file_attributes >> 16) & mode) != 0 rescue Errno::ENOENT false @@ -111,7 +110,7 @@ def size?(filename) def chown(owner, group, *filenames) filenames.each do |filename| - e = get_entry(filename) + e = find_entry(filename) e.extra.create('IUnix') unless e.extra.member?('IUnix') e.extra['IUnix'].uid = owner e.extra['IUnix'].gid = group @@ -121,7 +120,7 @@ def chown(owner, group, *filenames) def chmod(mode, *filenames) filenames.each do |filename| - e = get_entry(filename) + e = find_entry(filename) e.fstype = 3 # force convertion filesystem type to unix e.unix_perms = mode e.external_file_attributes = mode << 16 @@ -160,7 +159,7 @@ def join(*fragments) def utime(modified_time, *filenames) filenames.each do |filename| - get_entry(filename).time = modified_time + find_entry(filename).time = modified_time end end @@ -169,7 +168,7 @@ def mtime(filename) end def atime(filename) - e = get_entry(filename) + e = find_entry(filename) if e.extra.member? 'UniversalTime' e.extra['UniversalTime'].atime elsif e.extra.member? 'NTFS' @@ -178,7 +177,7 @@ def atime(filename) end def ctime(filename) - e = get_entry(filename) + e = find_entry(filename) if e.extra.member? 'UniversalTime' e.extra['UniversalTime'].ctime elsif e.extra.member? 'NTFS' diff --git a/lib/zip/filesystem/file_stat.rb b/lib/zip/filesystem/file_stat.rb index 6c43699d..169afdbd 100644 --- a/lib/zip/filesystem/file_stat.rb +++ b/lib/zip/filesystem/file_stat.rb @@ -34,13 +34,8 @@ def blocks nil end - def get_entry - @zip_fs_file.__send__(:get_entry, @entry_name) - end - private :get_entry - def gid - e = get_entry + e = find_entry if e.extra.member? 'IUnix' e.extra['IUnix'].gid || 0 else @@ -49,7 +44,7 @@ def gid end def uid - e = get_entry + e = find_entry if e.extra.member? 'IUnix' e.extra['IUnix'].uid || 0 else @@ -96,13 +91,19 @@ def blksize end def mode - e = get_entry + e = find_entry if e.fstype == 3 e.external_file_attributes >> 16 else 33_206 # 33206 is equivalent to -rw-rw-rw- end end + + private + + def find_entry + @zip_fs_file.find_entry(@entry_name) + end end end end From 26b7f98c0836b24cb053005a8a25da664831d052 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Thu, 3 Jun 2021 07:56:31 +0100 Subject: [PATCH 290/469] Use octal for more obvious definition of file-modes. --- lib/zip/filesystem/file_stat.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zip/filesystem/file_stat.rb b/lib/zip/filesystem/file_stat.rb index 169afdbd..22a6ac3e 100644 --- a/lib/zip/filesystem/file_stat.rb +++ b/lib/zip/filesystem/file_stat.rb @@ -95,7 +95,7 @@ def mode if e.fstype == 3 e.external_file_attributes >> 16 else - 33_206 # 33206 is equivalent to -rw-rw-rw- + 0o100_666 # Equivalent to -rw-rw-rw-. end end From a4e51f15fc0b8a3cd9331cef213940b2ba5b5dbe Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Thu, 3 Jun 2021 08:38:37 +0100 Subject: [PATCH 291/469] Use constants instead of literals for some `fstype` calls. --- lib/zip/filesystem/file.rb | 4 ++-- lib/zip/filesystem/file_stat.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/zip/filesystem/file.rb b/lib/zip/filesystem/file.rb index 76adad14..7a93f7e2 100644 --- a/lib/zip/filesystem/file.rb +++ b/lib/zip/filesystem/file.rb @@ -27,7 +27,7 @@ def find_entry(filename) def unix_mode_cmp(filename, mode) e = find_entry(filename) - e.fstype == 3 && ((e.external_file_attributes >> 16) & mode) != 0 + e.fstype == FSTYPE_UNIX && ((e.external_file_attributes >> 16) & mode) != 0 rescue Errno::ENOENT false end @@ -121,7 +121,7 @@ def chown(owner, group, *filenames) def chmod(mode, *filenames) filenames.each do |filename| e = find_entry(filename) - e.fstype = 3 # force convertion filesystem type to unix + e.fstype = FSTYPE_UNIX # Force conversion filesystem type to unix. e.unix_perms = mode e.external_file_attributes = mode << 16 e.dirty = true diff --git a/lib/zip/filesystem/file_stat.rb b/lib/zip/filesystem/file_stat.rb index 22a6ac3e..6117bb7c 100644 --- a/lib/zip/filesystem/file_stat.rb +++ b/lib/zip/filesystem/file_stat.rb @@ -92,7 +92,7 @@ def blksize def mode e = find_entry - if e.fstype == 3 + if e.fstype == FSTYPE_UNIX e.external_file_attributes >> 16 else 0o100_666 # Equivalent to -rw-rw-rw-. From 098bce399afacff6bb2534cca570eb97ca8463ef Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 6 Jun 2021 15:45:04 +0100 Subject: [PATCH 292/469] Update Changelog. --- Changelog.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 14b181ce..42eaa4d8 100644 --- a/Changelog.md +++ b/Changelog.md @@ -10,8 +10,9 @@ - Fix input stream partial read error. [#462](https://github.com/rubyzip/rubyzip/pull/462) - Fix zlib deflate buffer growth. [#447](https://github.com/rubyzip/rubyzip/pull/447) -Tooling: +Tooling/internal: +- Refactor, and tidy up, the `Zip::Filesystem` classes for improved maintainability. - Fix Windows tests. [#489](https://github.com/rubyzip/rubyzip/pull/489) - Refactor `assert_forwarded` so it does not need `ObjectSpace._id2ref` or `eval`. [#483](https://github.com/rubyzip/rubyzip/pull/483) - Add GitHub Actions CI infrastructure. [#469](https://github.com/rubyzip/rubyzip/issues/469) From 684b69f330c2b833d07aa8c6cbc579cd0974069f Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 31 May 2021 09:11:39 +0100 Subject: [PATCH 293/469] Move the restore options to the top level. This will ensure consistency between `File` and `Entry`. --- lib/zip.rb | 6 ++++++ lib/zip/entry.rb | 6 +++--- lib/zip/file.rb | 8 +------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/zip.rb b/lib/zip.rb index 9af80b15..7e700449 100644 --- a/lib/zip.rb +++ b/lib/zip.rb @@ -48,6 +48,12 @@ module Zip :force_entry_names_encoding, :validate_entry_sizes + DEFAULT_RESTORE_OPTIONS = { + restore_ownership: false, + restore_permissions: false, + restore_times: false + }.freeze + def reset! @_ran_once = false @unicode_names = false diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 2e484284..a22337fa 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -42,9 +42,9 @@ def set_default_vars_values end @follow_symlinks = false - @restore_times = false - @restore_permissions = false - @restore_ownership = false + @restore_times = DEFAULT_RESTORE_OPTIONS[:restore_times] + @restore_permissions = DEFAULT_RESTORE_OPTIONS[:restore_permissions] + @restore_ownership = DEFAULT_RESTORE_OPTIONS[:restore_ownership] # BUG: need an extra field to support uid/gid's @unix_uid = nil @unix_gid = nil diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 10a3f495..e45f852c 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -53,12 +53,6 @@ class File < CentralDirectory DATA_BUFFER_SIZE = 8192 IO_METHODS = [:tell, :seek, :read, :eof, :close].freeze - DEFAULT_OPTIONS = { - restore_ownership: false, - restore_permissions: false, - restore_times: false - }.freeze - attr_reader :name # default -> false. @@ -77,7 +71,7 @@ class File < CentralDirectory # a new archive if it doesn't exist already. def initialize(path_or_io, create = false, buffer = false, options = {}) super() - options = DEFAULT_OPTIONS + options = DEFAULT_RESTORE_OPTIONS .merge(compression_level: ::Zip.default_compression) .merge(options) @name = path_or_io.respond_to?(:path) ? path_or_io.path : path_or_io From a6c6345084eb607b5d23da79fc19670d27ab0354 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 31 May 2021 10:41:41 +0100 Subject: [PATCH 294/469] Set restoring permissions and times as the default. --- lib/zip.rb | 4 ++-- lib/zip/file.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/zip.rb b/lib/zip.rb index 7e700449..87a236a8 100644 --- a/lib/zip.rb +++ b/lib/zip.rb @@ -50,8 +50,8 @@ module Zip DEFAULT_RESTORE_OPTIONS = { restore_ownership: false, - restore_permissions: false, - restore_times: false + restore_permissions: true, + restore_times: true }.freeze def reset! diff --git a/lib/zip/file.rb b/lib/zip/file.rb index e45f852c..6286778f 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -58,10 +58,10 @@ class File < CentralDirectory # default -> false. attr_accessor :restore_ownership - # default -> false, but will be set to true in a future version. + # default -> true. attr_accessor :restore_permissions - # default -> false, but will be set to true in a future version. + # default -> true. attr_accessor :restore_times # Returns the zip files comment, if it has one From 3260e4e6668a179e4db282bb9a92abc7f3b30258 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 31 May 2021 17:16:55 +0100 Subject: [PATCH 295/469] Add some tests to ensure the default behaviour sticks. --- test/file_options_test.rb | 66 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/test/file_options_test.rb b/test/file_options_test.rb index 4bffa4f1..bf1803f3 100644 --- a/test/file_options_test.rb +++ b/test/file_options_test.rb @@ -23,7 +23,7 @@ def teardown ::File.unlink(TXTPATH_755) if ::File.exist?(TXTPATH_755) end - def test_restore_permissions + def test_restore_permissions_true # Copy and set up files with different permissions. ::FileUtils.cp(TXTPATH, TXTPATH_600) ::File.chmod(0o600, TXTPATH_600) @@ -47,6 +47,55 @@ def test_restore_permissions assert_equal(::File.stat(TXTPATH_755).mode, ::File.stat(EXTPATH_3).mode) end + def test_restore_permissions_false + # Copy and set up files with different permissions. + ::FileUtils.cp(TXTPATH, TXTPATH_600) + ::File.chmod(0o600, TXTPATH_600) + ::FileUtils.cp(TXTPATH, TXTPATH_755) + ::File.chmod(0o755, TXTPATH_755) + + ::Zip::File.open(ZIPPATH, true) do |zip| + zip.add(ENTRY_1, TXTPATH) + zip.add(ENTRY_2, TXTPATH_600) + zip.add(ENTRY_3, TXTPATH_755) + end + + ::Zip::File.open(ZIPPATH, false, restore_permissions: false) do |zip| + zip.extract(ENTRY_1, EXTPATH_1) + zip.extract(ENTRY_2, EXTPATH_2) + zip.extract(ENTRY_3, EXTPATH_3) + end + + default_perms = 0o100_666 - ::File.umask + assert_equal(default_perms, ::File.stat(EXTPATH_1).mode) + assert_equal(default_perms, ::File.stat(EXTPATH_2).mode) + assert_equal(default_perms, ::File.stat(EXTPATH_3).mode) + end + + def test_restore_permissions_as_default + # Copy and set up files with different permissions. + ::FileUtils.cp(TXTPATH, TXTPATH_600) + ::File.chmod(0o600, TXTPATH_600) + ::FileUtils.cp(TXTPATH, TXTPATH_755) + ::File.chmod(0o755, TXTPATH_755) + + ::Zip::File.open(ZIPPATH, true) do |zip| + zip.add(ENTRY_1, TXTPATH) + zip.add(ENTRY_2, TXTPATH_600) + zip.add(ENTRY_3, TXTPATH_755) + end + + ::Zip::File.open(ZIPPATH) do |zip| + zip.extract(ENTRY_1, EXTPATH_1) + zip.extract(ENTRY_2, EXTPATH_2) + zip.extract(ENTRY_3, EXTPATH_3) + end + + assert_equal(::File.stat(TXTPATH).mode, ::File.stat(EXTPATH_1).mode) + assert_equal(::File.stat(TXTPATH_600).mode, ::File.stat(EXTPATH_2).mode) + assert_equal(::File.stat(TXTPATH_755).mode, ::File.stat(EXTPATH_3).mode) + end + def test_restore_times_true ::Zip::File.open(ZIPPATH, true) do |zip| zip.add(ENTRY_1, TXTPATH) @@ -81,6 +130,21 @@ def test_restore_times_false assert_time_equal(::Time.now, ::File.mtime(EXTPATH_2)) end + def test_restore_times_true_as_default + ::Zip::File.open(ZIPPATH, true) do |zip| + zip.add(ENTRY_1, TXTPATH) + zip.add_stored(ENTRY_2, TXTPATH) + end + + ::Zip::File.open(ZIPPATH) do |zip| + zip.extract(ENTRY_1, EXTPATH_1) + zip.extract(ENTRY_2, EXTPATH_2) + end + + assert_time_equal(::File.mtime(TXTPATH), ::File.mtime(EXTPATH_1)) + assert_time_equal(::File.mtime(TXTPATH), ::File.mtime(EXTPATH_2)) + end + def test_get_find_consistency testzip = ::File.expand_path(::File.join('data', 'globTest.zip'), __dir__) file_f = ::File.expand_path('f_test.txt', Dir.tmpdir) From 2410f2889e69e23db23a2038772b7e0aaf99228e Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 31 May 2021 17:44:25 +0100 Subject: [PATCH 296/469] Restore file timestamps on all platforms. Was only being done on Unix-type filesystems for some reason. Moved code so that it is run for all files, whatever the underlying platform. --- lib/zip/entry.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index a22337fa..2327cd07 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -462,11 +462,6 @@ def set_unix_attributes_on_path(dest_path) unix_perms_mask = 0o7777 if @restore_ownership ::FileUtils.chmod(@unix_perms & unix_perms_mask, dest_path) if @restore_permissions && @unix_perms ::FileUtils.chown(@unix_uid, @unix_gid, dest_path) if @restore_ownership && @unix_uid && @unix_gid && ::Process.egid == 0 - - # Restore the timestamp on a file. This will either have come from the - # original source file that was copied into the archive, or from the - # creation date of the archive if there was no original source file. - ::FileUtils.touch(dest_path, mtime: time) if @restore_times end def set_extra_attributes_on_path(dest_path) # :nodoc: @@ -476,6 +471,11 @@ def set_extra_attributes_on_path(dest_path) # :nodoc: when ::Zip::FSTYPE_UNIX set_unix_attributes_on_path(dest_path) end + + # Restore the timestamp on a file. This will either have come from the + # original source file that was copied into the archive, or from the + # creation date of the archive if there was no original source file. + ::FileUtils.touch(dest_path, mtime: time) if @restore_times end def pack_c_dir_entry From 369056ff3071d09a8418f86d86c3037ec7e240b8 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 31 May 2021 18:15:39 +0100 Subject: [PATCH 297/469] Update Changelog. --- Changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changelog.md b/Changelog.md index 42eaa4d8..3e54456e 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,7 @@ # 3.0.0 (Next) +- Fix restore options consistency. [#486](https://github.com/rubyzip/rubyzip/pull/486) +- View and/or preserve original date created, date modified? (Windows). [#336](https://github.com/rubyzip/rubyzip/issues/336) - Fix frozen string literal error. [#475](https://github.com/rubyzip/rubyzip/pull/475) - Set the default `Entry` time to the file's mtime on Windows. [#465](https://github.com/rubyzip/rubyzip/issues/465) - Ensure that `Entry#time=` sets times as `DOSTime` objects. [#481](https://github.com/rubyzip/rubyzip/issues/481) From 52bcfc72f9d234363cbffba9b7ea4fa5333b3478 Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Sun, 6 Jun 2021 15:51:48 +0200 Subject: [PATCH 298/469] Revert "REVERT ME: This disables a test fixed by #486" This reverts commit 6ac2cb207d80a0900491538543c5a8589377f8cf. --- test/file_options_test.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/file_options_test.rb b/test/file_options_test.rb index bf1803f3..f82fa9ca 100644 --- a/test/file_options_test.rb +++ b/test/file_options_test.rb @@ -107,10 +107,6 @@ def test_restore_times_true zip.extract(ENTRY_2, EXTPATH_2) end - # this test is disabled on Windows for now, waiting for #486. - # please remove this after merging #486. - skip if Zip::RUNNING_ON_WINDOWS - assert_time_equal(::File.mtime(TXTPATH), ::File.mtime(EXTPATH_1)) assert_time_equal(::File.mtime(TXTPATH), ::File.mtime(EXTPATH_2)) end From 4a01537f32ffed29a53d34d84d6c444be1488c4e Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Sun, 6 Jun 2021 15:59:25 +0200 Subject: [PATCH 299/469] Fix restore permissons test on Windows --- test/file_options_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/file_options_test.rb b/test/file_options_test.rb index f82fa9ca..da9a402c 100644 --- a/test/file_options_test.rb +++ b/test/file_options_test.rb @@ -66,7 +66,7 @@ def test_restore_permissions_false zip.extract(ENTRY_3, EXTPATH_3) end - default_perms = 0o100_666 - ::File.umask + default_perms = (Zip::RUNNING_ON_WINDOWS ? 0o100_644 : 0o100_666) - ::File.umask assert_equal(default_perms, ::File.stat(EXTPATH_1).mode) assert_equal(default_perms, ::File.stat(EXTPATH_2).mode) assert_equal(default_perms, ::File.stat(EXTPATH_3).mode) From 4fe6bc89837fd50cc53e89d98db1e10aa6d57ede Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 6 Jun 2021 18:11:41 +0100 Subject: [PATCH 300/469] Update README to remove link to Travis. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f5cabcd0..5b20f664 100644 --- a/README.md +++ b/README.md @@ -348,7 +348,7 @@ rake Please also run `rubocop` over your changes. -Our CI currently runs on [Travis](https://travis-ci.org/github/rubyzip/rubyzip) and [GitHub Actions](https://github.com/rubyzip/rubyzip/actions). Please note that `rubocop` is run as part of the CI configuration and will fail a build if errors are found. +Our CI runs on [GitHub Actions](https://github.com/rubyzip/rubyzip/actions). Please note that `rubocop` is run as part of the CI configuration and will fail a build if errors are found. ## Website and Project Home From 51c6c10e7a9448fee340445c05b30bbd4cff95e9 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 6 Jun 2021 20:06:59 +0100 Subject: [PATCH 301/469] Add a compatibility table to the README. Fixes #455. --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 5b20f664..52ba31f3 100644 --- a/README.md +++ b/README.md @@ -332,6 +332,21 @@ You can set multiple settings at the same time by using a block: end ``` +## Compatibility + +Rubyzip is known to run on a number of platforms and under a number of different Ruby versions. Please see the table below for what we think the current situation is. Note: an empty cell means "unknown", not "does not work". + +| OS | 2.4 | 2.5 | 2.6 | 2.7 | 3.0 | Head | JRuby 9.2.17.0 | JRuby Head | Truffleruby 21.1.0 | Truffleruby Head | +|----|-----|-----|-----|-----|-----|------|----------------|------------|--------------------|------------------| +|Ubuntu 20.04| CI | CI | CI | CI | CI | ci | CI | ci | CI | ci | +|Mac OS 10.15.7| CI | x | x | x | x | | x | | x | | +|Windows 10| | | | x | | | | | | | +|Windows Server 2019| CI | | | | | | | | | | + +Key: `CI` - tested in CI, should work; `ci` - tested in CI, might fail; `x` - known working; `o` - known failing. + +Please [raise a PR](https://github.com/rubyzip/rubyzip/pulls) if you know Rubyzip works on a platform/Ruby combination not listed here, or [raise an issue](https://github.com/rubyzip/rubyzip/issues) if you see a failure where we think it should work. + ## Developing Install the dependencies: From cd9a3fcad1a46bcaf29a30033a11ad6dff72211a Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Thu, 10 Jun 2021 17:29:00 +0100 Subject: [PATCH 302/469] Move all the `read_zip_*` methods out of `Entry`. They were only ever used in `CentralDirectory` anyway. --- lib/zip/central_directory.rb | 46 +++++++++++++++++++++++------------- lib/zip/entry.rb | 12 ---------- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/lib/zip/central_directory.rb b/lib/zip/central_directory.rb index 36a150ba..d6f5b0c5 100644 --- a/lib/zip/central_directory.rb +++ b/lib/zip/central_directory.rb @@ -88,28 +88,28 @@ def write_64_eocd_locator(io, zip64_eocd_offset) def read_64_e_o_c_d(buf) #:nodoc: buf = get_64_e_o_c_d(buf) - @size_of_zip64_e_o_c_d = Entry.read_zip_64_long(buf) - @version_made_by = Entry.read_zip_short(buf) - @version_needed_for_extract = Entry.read_zip_short(buf) - @number_of_this_disk = Entry.read_zip_long(buf) - @number_of_disk_with_start_of_cdir = Entry.read_zip_long(buf) - @total_number_of_entries_in_cdir_on_this_disk = Entry.read_zip_64_long(buf) - @size = Entry.read_zip_64_long(buf) - @size_in_bytes = Entry.read_zip_64_long(buf) - @cdir_offset = Entry.read_zip_64_long(buf) + @size_of_zip64_e_o_c_d = read_long64(buf) + @version_made_by = read_short(buf) + @version_needed_for_extract = read_short(buf) + @number_of_this_disk = read_long(buf) + @number_of_disk_with_start_of_cdir = read_long(buf) + @total_number_of_entries_in_cdir_on_this_disk = read_long64(buf) + @size = read_long64(buf) + @size_in_bytes = read_long64(buf) + @cdir_offset = read_long64(buf) @zip_64_extensible = buf.slice!(0, buf.bytesize) raise Error, 'Zip consistency problem while reading eocd structure' unless buf.empty? end def read_e_o_c_d(buf) #:nodoc: buf = get_e_o_c_d(buf) - @number_of_this_disk = Entry.read_zip_short(buf) - @number_of_disk_with_start_of_cdir = Entry.read_zip_short(buf) - @total_number_of_entries_in_cdir_on_this_disk = Entry.read_zip_short(buf) - @size = Entry.read_zip_short(buf) - @size_in_bytes = Entry.read_zip_long(buf) - @cdir_offset = Entry.read_zip_long(buf) - comment_length = Entry.read_zip_short(buf) + @number_of_this_disk = read_short(buf) + @number_of_disk_with_start_of_cdir = read_short(buf) + @total_number_of_entries_in_cdir_on_this_disk = read_short(buf) + @size = read_short(buf) + @size_in_bytes = read_long(buf) + @cdir_offset = read_long(buf) + comment_length = read_short(buf) @comment = if comment_length.to_i <= 0 buf.slice!(0, buf.size) else @@ -233,6 +233,20 @@ def ==(other) #:nodoc: @entry_set.entries.sort == other.entries.sort && comment == other.comment end + + private + + def read_short(io) # :nodoc: + io.read(2).unpack1('v') + end + + def read_long(io) # :nodoc: + io.read(4).unpack1('V') + end + + def read_long64(io) # :nodoc: + io.read(8).unpack1('Q<') + end end end diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 2327cd07..cdff9baa 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -230,18 +230,6 @@ def to_s end class << self - def read_zip_short(io) # :nodoc: - io.read(2).unpack1('v') - end - - def read_zip_long(io) # :nodoc: - io.read(4).unpack1('V') - end - - def read_zip_64_long(io) # :nodoc: - io.read(8).unpack1('Q<') - end - def read_c_dir_entry(io) #:nodoc:all path = if io.respond_to?(:path) io.path From 7e254dc581af8f27ffcbe6b18a52662daf13b7a6 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Thu, 10 Jun 2021 22:44:51 +0100 Subject: [PATCH 303/469] Refactor unpacking the eocd record. The old version used some really obfuscated code to perform what is an essentially fairly simple job. --- lib/zip/central_directory.rb | 50 +++++++++++++++--------------------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/lib/zip/central_directory.rb b/lib/zip/central_directory.rb index d6f5b0c5..51f14370 100644 --- a/lib/zip/central_directory.rb +++ b/lib/zip/central_directory.rb @@ -101,21 +101,26 @@ def read_64_e_o_c_d(buf) #:nodoc: raise Error, 'Zip consistency problem while reading eocd structure' unless buf.empty? end - def read_e_o_c_d(buf) #:nodoc: - buf = get_e_o_c_d(buf) - @number_of_this_disk = read_short(buf) - @number_of_disk_with_start_of_cdir = read_short(buf) - @total_number_of_entries_in_cdir_on_this_disk = read_short(buf) - @size = read_short(buf) - @size_in_bytes = read_long(buf) - @cdir_offset = read_long(buf) - comment_length = read_short(buf) - @comment = if comment_length.to_i <= 0 - buf.slice!(0, buf.size) - else - buf.read(comment_length) - end - raise Error, 'Zip consistency problem while reading eocd structure' unless buf.empty? + def unpack_e_o_c_d(buffer) #:nodoc: + index = buffer.rindex([END_OF_CDS].pack('V')) + raise Error, 'Zip end of central directory signature not found' unless index + + buf = buffer.slice(index, buffer.size) + + _, # END_OF_CDS signature. We know we have this at this point. + @number_of_this_disk, + @number_of_disk_with_start_of_cdir, + @total_number_of_entries_in_cdir_on_this_disk, + @size, + @size_in_bytes, + @cdir_offset, + comment_length = buf.unpack('VvvvvVVv') + + @comment = if comment_length.positive? + buf.slice(STATIC_EOCD_SIZE, comment_length) + else + '' + end end def read_central_directory_entries(io) #:nodoc: @@ -162,24 +167,11 @@ def read_from_stream(io) #:nodoc: if zip64_file?(buf) read_64_e_o_c_d(buf) else - read_e_o_c_d(buf) + unpack_e_o_c_d(buf) end read_central_directory_entries(io) end - def get_e_o_c_d(buf) #:nodoc: - sig_index = buf.rindex([END_OF_CDS].pack('V')) - raise Error, 'Zip end of central directory signature not found' unless sig_index - - buf = buf.slice!((sig_index + 4)..(buf.bytesize)) - - def buf.read(count) - slice!(0, count) - end - - buf - end - def zip64_file?(buf) buf.rindex([ZIP64_END_OF_CDS].pack('V')) && buf.rindex([ZIP64_EOCD_LOCATOR].pack('V')) end From dc27c99eb146afafb972526887b78b47de2bdabc Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Fri, 11 Jun 2021 13:50:09 +0100 Subject: [PATCH 304/469] Refactor unpacking the Zip64 eocd record. --- lib/zip/central_directory.rb | 62 +++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/lib/zip/central_directory.rb b/lib/zip/central_directory.rb index 51f14370..314b246f 100644 --- a/lib/zip/central_directory.rb +++ b/lib/zip/central_directory.rb @@ -9,6 +9,7 @@ class CentralDirectory ZIP64_EOCD_LOCATOR = 0x07064b50 MAX_END_OF_CDS_SIZE = 65_536 + 18 STATIC_EOCD_SIZE = 22 + ZIP64_STATIC_EOCD_SIZE = 56 attr_reader :comment @@ -86,19 +87,36 @@ def write_64_eocd_locator(io, zip64_eocd_offset) private :write_64_eocd_locator - def read_64_e_o_c_d(buf) #:nodoc: - buf = get_64_e_o_c_d(buf) - @size_of_zip64_e_o_c_d = read_long64(buf) - @version_made_by = read_short(buf) - @version_needed_for_extract = read_short(buf) - @number_of_this_disk = read_long(buf) - @number_of_disk_with_start_of_cdir = read_long(buf) - @total_number_of_entries_in_cdir_on_this_disk = read_long64(buf) - @size = read_long64(buf) - @size_in_bytes = read_long64(buf) - @cdir_offset = read_long64(buf) - @zip_64_extensible = buf.slice!(0, buf.bytesize) - raise Error, 'Zip consistency problem while reading eocd structure' unless buf.empty? + def unpack_64_e_o_c_d(buffer) #:nodoc: + index = buffer.rindex([ZIP64_END_OF_CDS].pack('V')) + raise Error, 'Zip64 end of central directory signature not found' unless index + + l_index = buffer.rindex([ZIP64_EOCD_LOCATOR].pack('V')) + raise Error, 'Zip64 end of central directory signature locator not found' unless l_index + + buf = buffer.slice(index..l_index) + + _, # ZIP64_END_OF_CDS signature. We know we have this at this point. + @size_of_zip64_e_o_c_d, + @version_made_by, + @version_needed_for_extract, + @number_of_this_disk, + @number_of_disk_with_start_of_cdir, + @total_number_of_entries_in_cdir_on_this_disk, + @size, + @size_in_bytes, + @cdir_offset = buf.unpack('VQ Date: Fri, 11 Jun 2021 13:51:40 +0100 Subject: [PATCH 305/469] Remove the now redundant `read_zip_*` methods. We're unpacking headers in chunks now, using `unpack`. --- lib/zip/central_directory.rb | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/lib/zip/central_directory.rb b/lib/zip/central_directory.rb index 314b246f..f6e6cd9c 100644 --- a/lib/zip/central_directory.rb +++ b/lib/zip/central_directory.rb @@ -227,20 +227,6 @@ def ==(other) #:nodoc: @entry_set.entries.sort == other.entries.sort && comment == other.comment end - - private - - def read_short(io) # :nodoc: - io.read(2).unpack1('v') - end - - def read_long(io) # :nodoc: - io.read(4).unpack1('V') - end - - def read_long64(io) # :nodoc: - io.read(8).unpack1('Q<') - end end end From 3fbb48de31dade45a1cb7c303aa31034f7d5f1e4 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Fri, 11 Jun 2021 17:03:43 +0100 Subject: [PATCH 306/469] Refactor the full 64bit tests. --- test/zip64_full_test.rb | 82 +++++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/test/zip64_full_test.rb b/test/zip64_full_test.rb index 6f900bb4..f1429a01 100644 --- a/test/zip64_full_test.rb +++ b/test/zip64_full_test.rb @@ -1,53 +1,57 @@ # frozen_string_literal: true -if ENV['FULL_ZIP64_TEST'] - require 'minitest/autorun' - require 'minitest/unit' - require 'fileutils' - require 'zip' - - # test zip64 support for real, by actually exceeding the 32-bit size/offset limits - # this test does not, of course, run with the normal unit tests! ;) - - class Zip64FullTest < MiniTest::Test - def teardown - ::Zip.reset! - end +require 'test_helper' - def prepare_test_file(test_filename) - ::File.delete(test_filename) if ::File.exist?(test_filename) - test_filename - end +# Test zip64 support for real by actually exceeding the 32-bit +# size/offset limits. This test does not, of course, run with the +# normal unit tests! ;) +class Zip64FullTest < MiniTest::Test + HUGE_ZIP = 'huge.zip' + + def teardown + ::Zip.reset! + ::FileUtils.rm_f HUGE_ZIP + end - def test_large_zip_file - ::Zip.write_zip64_support = true - first_text = 'starting out small' - last_text = 'this tests files starting after 4GB in the archive' - test_filename = prepare_test_file('huge.zip') - ::Zip::OutputStream.open(test_filename) do |io| - io.put_next_entry('first_file.txt') + def test_large_zip_file + skip unless ENV['FULL_ZIP64_TEST'] && !Zip::RUNNING_ON_WINDOWS + + ::Zip.write_zip64_support = true + first_text = 'starting out small' + last_text = 'this tests files starting after 4GB in the archive' + + ::Zip::File.open(HUGE_ZIP, ::Zip::File::CREATE) do |zf| + zf.get_output_stream('first_file.txt') do |io| io.write(first_text) + end - # write just over 4GB (stored, so the zip file exceeds 4GB) - buf = 'blah' * 16_384 - io.put_next_entry('huge_file', nil, nil, ::Zip::Entry::STORED) + # Write just over 4GB (stored, so the zip file exceeds 4GB). + buf = 'blah' * 16_384 + zf.get_output_stream( + 'huge_file', nil, nil, nil, nil, nil, ::Zip::Entry::STORED + ) do |io| 65_537.times { io.write(buf) } - - io.put_next_entry('last_file.txt') - io.write(last_text) end - ::Zip::File.open(test_filename) do |zf| - assert_equal %w[first_file.txt huge_file last_file.txt], zf.entries.map(&:name) - assert_equal first_text, zf.read('first_file.txt') - assert_equal last_text, zf.read('last_file.txt') + zf.get_output_stream('last_file.txt') do |io| + io.write(last_text) end + end - # NOTE: if this fails, be sure you have UnZip version 6.0 or newer - # as this is the first version to support zip64 extensions - # but some OSes (*cough* OSX) still bundle a 5.xx release - assert system("unzip -tqq #{test_filename}"), 'third-party zip validation failed' + ::Zip::File.open(HUGE_ZIP) do |zf| + assert_equal( + %w[first_file.txt huge_file last_file.txt], zf.entries.map(&:name) + ) + assert_equal(first_text, zf.read('first_file.txt')) + assert_equal(last_text, zf.read('last_file.txt')) end - end + # NOTE: if this fails, be sure you have UnZip version 6.0 or newer + # as this is the first version to support zip64 extensions + # but some OSes (*cough* OSX) still bundle a 5.xx release + assert( + system("unzip -tqq #{HUGE_ZIP}"), + 'third-party zip validation failed' + ) + end end From be1c5b7c03b52b872bc9494f6e0de698c09dffa7 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Fri, 11 Jun 2021 17:14:49 +0100 Subject: [PATCH 307/469] Turn on FULL_ZIP64_TEST in CI. --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bc4c0710..8b2abc0e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -33,6 +33,7 @@ jobs: - name: Run the tests env: JRUBY_OPTS: --debug + FULL_ZIP64_TEST: 1 run: bundle exec rake - name: Coveralls From 7df623fb0ec3f1781305d6e0028f28132e5d1417 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Fri, 11 Jun 2021 23:23:34 +0100 Subject: [PATCH 308/469] Read EOCD record for Zip64 files. Means we actually read in the file-level comment now! Fixes #492. --- lib/zip/central_directory.rb | 26 +++++++++++++++----------- test/zip64_full_test.rb | 4 ++++ 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/lib/zip/central_directory.rb b/lib/zip/central_directory.rb index f6e6cd9c..d54e5670 100644 --- a/lib/zip/central_directory.rb +++ b/lib/zip/central_directory.rb @@ -126,14 +126,21 @@ def unpack_e_o_c_d(buffer) #:nodoc: buf = buffer.slice(index, buffer.size) _, # END_OF_CDS signature. We know we have this at this point. - @number_of_this_disk, - @number_of_disk_with_start_of_cdir, - @total_number_of_entries_in_cdir_on_this_disk, - @size, - @size_in_bytes, - @cdir_offset, + num_disk, + num_disk_cdir, + num_cdir_disk, + num_entries, + size_in_bytes, + cdir_offset, comment_length = buf.unpack('VvvvvVVv') + @number_of_this_disk = num_disk unless num_disk == 0xFFFF + @number_of_disk_with_start_of_cdir = num_disk_cdir unless num_disk_cdir == 0xFFFF + @total_number_of_entries_in_cdir_on_this_disk = num_cdir_disk unless num_cdir_disk == 0xFFFF + @size = num_entries unless num_entries == 0xFFFF + @size_in_bytes = size_in_bytes unless size_in_bytes == 0xFFFFFFFF + @cdir_offset = cdir_offset unless cdir_offset == 0xFFFFFFFF + @comment = if comment_length.positive? buf.slice(STATIC_EOCD_SIZE, comment_length) else @@ -182,11 +189,8 @@ def read_local_extra_field(io) def read_from_stream(io) #:nodoc: buf = start_buf(io) - if zip64_file?(buf) - unpack_64_e_o_c_d(buf) - else - unpack_e_o_c_d(buf) - end + unpack_64_e_o_c_d(buf) if zip64_file?(buf) + unpack_e_o_c_d(buf) read_central_directory_entries(io) end diff --git a/test/zip64_full_test.rb b/test/zip64_full_test.rb index f1429a01..5d174e3b 100644 --- a/test/zip64_full_test.rb +++ b/test/zip64_full_test.rb @@ -19,8 +19,11 @@ def test_large_zip_file ::Zip.write_zip64_support = true first_text = 'starting out small' last_text = 'this tests files starting after 4GB in the archive' + comment_text = 'this is a file comment in a zip64 archive' ::Zip::File.open(HUGE_ZIP, ::Zip::File::CREATE) do |zf| + zf.comment = comment_text + zf.get_output_stream('first_file.txt') do |io| io.write(first_text) end @@ -44,6 +47,7 @@ def test_large_zip_file ) assert_equal(first_text, zf.read('first_file.txt')) assert_equal(last_text, zf.read('last_file.txt')) + assert_equal(comment_text, zf.comment) end # NOTE: if this fails, be sure you have UnZip version 6.0 or newer From bd2f15e4bb1211c58bcf29f919fa81ef044e14a7 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 12 Jun 2021 10:33:16 +0100 Subject: [PATCH 309/469] Extract the `Zip::File::split` code into its own module. This code is rarely used and may not even be correct according to the standard. Also this de-clutters the `File` class. --- .rubocop_todo.yml | 3 ++ lib/zip/file.rb | 85 ++-------------------------------------- lib/zip/file_split.rb | 86 +++++++++++++++++++++++++++++++++++++++++ test/file_split_test.rb | 2 +- 4 files changed, 94 insertions(+), 82 deletions(-) create mode 100644 lib/zip/file_split.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index aafa7fe7..4e443b4d 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -75,6 +75,7 @@ Style/IfUnlessModifier: Exclude: - 'lib/zip/entry.rb' - 'lib/zip/file.rb' + - 'lib/zip/file_split.rb' - 'lib/zip/filesystem/dir.rb' - 'lib/zip/filesystem/file.rb' - 'lib/zip/pass_thru_decompressor.rb' @@ -100,6 +101,7 @@ Style/NumericPredicate: - 'lib/zip/extra_field/universal_time.rb' - 'lib/zip/extra_field/unix.rb' - 'lib/zip/file.rb' + - 'lib/zip/file_split.rb' - 'lib/zip/filesystem/file.rb' - 'lib/zip/input_stream.rb' - 'lib/zip/ioextras.rb' @@ -114,6 +116,7 @@ Style/OptionalBooleanParameter: Exclude: - 'lib/zip/entry.rb' - 'lib/zip/file.rb' + - 'lib/zip/file_split.rb' - 'lib/zip/output_stream.rb' # Offense count: 29 diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 6286778f..6665698a 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require_relative 'file_split' + module Zip # ZipFile is modeled after java.util.zip.ZipFile from the Java SDK. # The most important methods are those inherited from @@ -45,12 +47,9 @@ module Zip # interface for accessing the filesystem, ie. the File and Dir classes. class File < CentralDirectory + extend FileSplit + CREATE = true - SPLIT_SIGNATURE = 0x08074b50 - ZIP64_EOCD_SIGNATURE = 0x06064b50 - MAX_SEGMENT_SIZE = 3_221_225_472 - MIN_SEGMENT_SIZE = 65_536 - DATA_BUFFER_SIZE = 8192 IO_METHODS = [:tell, :seek, :read, :eof, :close].freeze attr_reader :name @@ -172,82 +171,6 @@ def foreach(zip_file_name, &block) zip_file.each(&block) end end - - def get_segment_size_for_split(segment_size) - if MIN_SEGMENT_SIZE > segment_size - MIN_SEGMENT_SIZE - elsif MAX_SEGMENT_SIZE < segment_size - MAX_SEGMENT_SIZE - else - segment_size - end - end - - def get_partial_zip_file_name(zip_file_name, partial_zip_file_name) - unless partial_zip_file_name.nil? - partial_zip_file_name = zip_file_name.sub(/#{::File.basename(zip_file_name)}\z/, - partial_zip_file_name + ::File.extname(zip_file_name)) - end - partial_zip_file_name ||= zip_file_name - partial_zip_file_name - end - - def get_segment_count_for_split(zip_file_size, segment_size) - (zip_file_size / segment_size).to_i + (zip_file_size % segment_size == 0 ? 0 : 1) - end - - def put_split_signature(szip_file, segment_size) - signature_packed = [SPLIT_SIGNATURE].pack('V') - szip_file << signature_packed - segment_size - signature_packed.size - end - - # - # TODO: Make the code more understandable - # - def save_splited_part(zip_file, partial_zip_file_name, zip_file_size, szip_file_index, segment_size, segment_count) - ssegment_size = zip_file_size - zip_file.pos - ssegment_size = segment_size if ssegment_size > segment_size - szip_file_name = "#{partial_zip_file_name}.#{format('%03d', szip_file_index)}" - ::File.open(szip_file_name, 'wb') do |szip_file| - if szip_file_index == 1 - ssegment_size = put_split_signature(szip_file, segment_size) - end - chunk_bytes = 0 - until ssegment_size == chunk_bytes || zip_file.eof? - segment_bytes_left = ssegment_size - chunk_bytes - buffer_size = segment_bytes_left < DATA_BUFFER_SIZE ? segment_bytes_left : DATA_BUFFER_SIZE - chunk = zip_file.read(buffer_size) - chunk_bytes += buffer_size - szip_file << chunk - # Info for track splitting - yield segment_count, szip_file_index, chunk_bytes, ssegment_size if block_given? - end - end - end - - # Splits an archive into parts with segment size - def split(zip_file_name, segment_size = MAX_SEGMENT_SIZE, delete_zip_file = true, partial_zip_file_name = nil) - raise Error, "File #{zip_file_name} not found" unless ::File.exist?(zip_file_name) - raise Errno::ENOENT, zip_file_name unless ::File.readable?(zip_file_name) - - zip_file_size = ::File.size(zip_file_name) - segment_size = get_segment_size_for_split(segment_size) - return if zip_file_size <= segment_size - - segment_count = get_segment_count_for_split(zip_file_size, segment_size) - ::Zip::File.open(zip_file_name) {} # Check for correct zip structure. - partial_zip_file_name = get_partial_zip_file_name(zip_file_name, partial_zip_file_name) - szip_file_index = 0 - ::File.open(zip_file_name, 'rb') do |zip_file| - until zip_file.eof? - szip_file_index += 1 - save_splited_part(zip_file, partial_zip_file_name, zip_file_size, szip_file_index, segment_size, segment_count) - end - end - ::File.delete(zip_file_name) if delete_zip_file - szip_file_index - end end # Returns an input stream to the specified entry. If a block is passed diff --git a/lib/zip/file_split.rb b/lib/zip/file_split.rb new file mode 100644 index 00000000..5af3514f --- /dev/null +++ b/lib/zip/file_split.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +module Zip + module FileSplit #:nodoc: + SPLIT_SIGNATURE = 0x08074b50 + MAX_SEGMENT_SIZE = 3_221_225_472 + MIN_SEGMENT_SIZE = 65_536 + DATA_BUFFER_SIZE = 8192 + + def get_segment_size_for_split(segment_size) + if MIN_SEGMENT_SIZE > segment_size + MIN_SEGMENT_SIZE + elsif MAX_SEGMENT_SIZE < segment_size + MAX_SEGMENT_SIZE + else + segment_size + end + end + + def get_partial_zip_file_name(zip_file_name, partial_zip_file_name) + unless partial_zip_file_name.nil? + partial_zip_file_name = zip_file_name.sub(/#{::File.basename(zip_file_name)}\z/, + partial_zip_file_name + ::File.extname(zip_file_name)) + end + partial_zip_file_name ||= zip_file_name + partial_zip_file_name + end + + def get_segment_count_for_split(zip_file_size, segment_size) + (zip_file_size / segment_size).to_i + (zip_file_size % segment_size == 0 ? 0 : 1) + end + + def put_split_signature(szip_file, segment_size) + signature_packed = [SPLIT_SIGNATURE].pack('V') + szip_file << signature_packed + segment_size - signature_packed.size + end + + # + # TODO: Make the code more understandable + # + def save_splited_part(zip_file, partial_zip_file_name, zip_file_size, szip_file_index, segment_size, segment_count) + ssegment_size = zip_file_size - zip_file.pos + ssegment_size = segment_size if ssegment_size > segment_size + szip_file_name = "#{partial_zip_file_name}.#{format('%03d', szip_file_index)}" + ::File.open(szip_file_name, 'wb') do |szip_file| + if szip_file_index == 1 + ssegment_size = put_split_signature(szip_file, segment_size) + end + chunk_bytes = 0 + until ssegment_size == chunk_bytes || zip_file.eof? + segment_bytes_left = ssegment_size - chunk_bytes + buffer_size = segment_bytes_left < DATA_BUFFER_SIZE ? segment_bytes_left : DATA_BUFFER_SIZE + chunk = zip_file.read(buffer_size) + chunk_bytes += buffer_size + szip_file << chunk + # Info for track splitting + yield segment_count, szip_file_index, chunk_bytes, ssegment_size if block_given? + end + end + end + + # Splits an archive into parts with segment size + def split(zip_file_name, segment_size = MAX_SEGMENT_SIZE, delete_zip_file = true, partial_zip_file_name = nil) + raise Error, "File #{zip_file_name} not found" unless ::File.exist?(zip_file_name) + raise Errno::ENOENT, zip_file_name unless ::File.readable?(zip_file_name) + + zip_file_size = ::File.size(zip_file_name) + segment_size = get_segment_size_for_split(segment_size) + return if zip_file_size <= segment_size + + segment_count = get_segment_count_for_split(zip_file_size, segment_size) + ::Zip::File.open(zip_file_name) {} # Check for correct zip structure. + partial_zip_file_name = get_partial_zip_file_name(zip_file_name, partial_zip_file_name) + szip_file_index = 0 + ::File.open(zip_file_name, 'rb') do |zip_file| + until zip_file.eof? + szip_file_index += 1 + save_splited_part(zip_file, partial_zip_file_name, zip_file_size, szip_file_index, segment_size, segment_count) + end + end + ::File.delete(zip_file_name) if delete_zip_file + szip_file_index + end + end +end diff --git a/test/file_split_test.rb b/test/file_split_test.rb index 50fc4a4b..fbe7bff1 100644 --- a/test/file_split_test.rb +++ b/test/file_split_test.rb @@ -33,7 +33,7 @@ def test_split Dir["#{TEST_ZIP.zip_name}.*"].sort.each_with_index do |zip_file_name, index| File.open(zip_file_name, 'rb') do |zip_file| - zip_file.read([::Zip::File::SPLIT_SIGNATURE].pack('V').size) if index == 0 + zip_file.read([::Zip::FileSplit::SPLIT_SIGNATURE].pack('V').size) if index == 0 File.open(UNSPLITTED_FILENAME, 'ab') do |file| file << zip_file.read end From 80382135e58061ff270d14b0d6491eb1a71320f7 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 12 Jun 2021 11:13:45 +0100 Subject: [PATCH 310/469] Tidy up some of the file split code. --- .rubocop_todo.yml | 2 -- lib/zip/file_split.rb | 9 ++++++--- test/file_split_test.rb | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 4e443b4d..e043313c 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -101,12 +101,10 @@ Style/NumericPredicate: - 'lib/zip/extra_field/universal_time.rb' - 'lib/zip/extra_field/unix.rb' - 'lib/zip/file.rb' - - 'lib/zip/file_split.rb' - 'lib/zip/filesystem/file.rb' - 'lib/zip/input_stream.rb' - 'lib/zip/ioextras.rb' - 'lib/zip/ioextras/abstract_input_stream.rb' - - 'test/file_split_test.rb' - 'test/test_helper.rb' # Offense count: 6 diff --git a/lib/zip/file_split.rb b/lib/zip/file_split.rb index 5af3514f..68ac0248 100644 --- a/lib/zip/file_split.rb +++ b/lib/zip/file_split.rb @@ -19,15 +19,18 @@ def get_segment_size_for_split(segment_size) def get_partial_zip_file_name(zip_file_name, partial_zip_file_name) unless partial_zip_file_name.nil? - partial_zip_file_name = zip_file_name.sub(/#{::File.basename(zip_file_name)}\z/, - partial_zip_file_name + ::File.extname(zip_file_name)) + partial_zip_file_name = zip_file_name.sub( + /#{::File.basename(zip_file_name)}\z/, + partial_zip_file_name + ::File.extname(zip_file_name) + ) end partial_zip_file_name ||= zip_file_name partial_zip_file_name end def get_segment_count_for_split(zip_file_size, segment_size) - (zip_file_size / segment_size).to_i + (zip_file_size % segment_size == 0 ? 0 : 1) + (zip_file_size / segment_size).to_i + + ((zip_file_size % segment_size).zero? ? 0 : 1) end def put_split_signature(szip_file, segment_size) diff --git a/test/file_split_test.rb b/test/file_split_test.rb index fbe7bff1..92861837 100644 --- a/test/file_split_test.rb +++ b/test/file_split_test.rb @@ -33,7 +33,7 @@ def test_split Dir["#{TEST_ZIP.zip_name}.*"].sort.each_with_index do |zip_file_name, index| File.open(zip_file_name, 'rb') do |zip_file| - zip_file.read([::Zip::FileSplit::SPLIT_SIGNATURE].pack('V').size) if index == 0 + zip_file.read([::Zip::FileSplit::SPLIT_SIGNATURE].pack('V').size) if index.zero? File.open(UNSPLITTED_FILENAME, 'ab') do |file| file << zip_file.read end From 21ba82c67ce6aa7e6ccfb53e78616c8f00919b56 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 12 Jun 2021 16:27:45 +0100 Subject: [PATCH 311/469] Move the split signature to the constants file. --- lib/zip/constants.rb | 2 ++ lib/zip/file_split.rb | 9 ++++----- test/file_split_test.rb | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/zip/constants.rb b/lib/zip/constants.rb index c15aeae2..d0c5927e 100644 --- a/lib/zip/constants.rb +++ b/lib/zip/constants.rb @@ -13,6 +13,8 @@ module Zip VERSION_NEEDED_TO_EXTRACT = 20 VERSION_NEEDED_TO_EXTRACT_ZIP64 = 45 + SPLIT_FILE_SIGNATURE = 0x08074b50 + FILE_TYPE_FILE = 0o10 FILE_TYPE_DIR = 0o04 FILE_TYPE_SYMLINK = 0o12 diff --git a/lib/zip/file_split.rb b/lib/zip/file_split.rb index 68ac0248..841aab0b 100644 --- a/lib/zip/file_split.rb +++ b/lib/zip/file_split.rb @@ -2,10 +2,9 @@ module Zip module FileSplit #:nodoc: - SPLIT_SIGNATURE = 0x08074b50 - MAX_SEGMENT_SIZE = 3_221_225_472 - MIN_SEGMENT_SIZE = 65_536 - DATA_BUFFER_SIZE = 8192 + MAX_SEGMENT_SIZE = 3_221_225_472 + MIN_SEGMENT_SIZE = 65_536 + DATA_BUFFER_SIZE = 8192 def get_segment_size_for_split(segment_size) if MIN_SEGMENT_SIZE > segment_size @@ -34,7 +33,7 @@ def get_segment_count_for_split(zip_file_size, segment_size) end def put_split_signature(szip_file, segment_size) - signature_packed = [SPLIT_SIGNATURE].pack('V') + signature_packed = [SPLIT_FILE_SIGNATURE].pack('V') szip_file << signature_packed segment_size - signature_packed.size end diff --git a/test/file_split_test.rb b/test/file_split_test.rb index 92861837..517723dc 100644 --- a/test/file_split_test.rb +++ b/test/file_split_test.rb @@ -33,7 +33,7 @@ def test_split Dir["#{TEST_ZIP.zip_name}.*"].sort.each_with_index do |zip_file_name, index| File.open(zip_file_name, 'rb') do |zip_file| - zip_file.read([::Zip::FileSplit::SPLIT_SIGNATURE].pack('V').size) if index.zero? + zip_file.read([::Zip::SPLIT_FILE_SIGNATURE].pack('V').size) if index.zero? File.open(UNSPLITTED_FILENAME, 'ab') do |file| file << zip_file.read end From 750c47461048cd098ed93ad1adca9ba08812dd73 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 12 Jun 2021 16:31:50 +0100 Subject: [PATCH 312/469] Update Changelog. --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index 3e54456e..4036881d 100644 --- a/Changelog.md +++ b/Changelog.md @@ -14,6 +14,7 @@ Tooling/internal: +- Extract the file splitting code out into its own module. - Refactor, and tidy up, the `Zip::Filesystem` classes for improved maintainability. - Fix Windows tests. [#489](https://github.com/rubyzip/rubyzip/pull/489) - Refactor `assert_forwarded` so it does not need `ObjectSpace._id2ref` or `eval`. [#483](https://github.com/rubyzip/rubyzip/pull/483) From bf3ae2ad7614a4661507018691acf48ef9c74e2b Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Fri, 18 Jun 2021 11:11:11 +0100 Subject: [PATCH 313/469] Improve some entry header tests. Use `StringIO` instead of the custom `IOizeString` code in the test_helper. Also test both versions (class and instance) of the `Entry` APIs. --- test/central_directory_entry_test.rb | 25 +++++++++++++++++-------- test/local_entry_test.rb | 27 +++++++++++++++++---------- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/test/central_directory_entry_test.rb b/test/central_directory_entry_test.rb index a6898c5c..e8af639c 100644 --- a/test/central_directory_entry_test.rb +++ b/test/central_directory_entry_test.rb @@ -59,13 +59,22 @@ def test_read_from_stream end end - def test_read_entry_from_truncated_zip_file - fragment = '' - File.open('test/data/testDirectory.bin') { |f| fragment = f.read(12) } # cdir entry header is at least 46 bytes - fragment.extend(IOizeString) - entry = ::Zip::Entry.new - entry.read_c_dir_entry(fragment) - raise 'ZipError expected' - rescue ::Zip::Error + def test_read_entry_from_truncated_zip_file_raises_error + File.open('test/data/testDirectory.bin') do |f| + # cdir entry header is at least 46 bytes, so just read a bit. + fragment = f.read(12) + assert_raises(::Zip::Error) do + entry = ::Zip::Entry.new + entry.read_c_dir_entry(StringIO.new(fragment)) + end + end + end + + def test_read_entry_from_truncated_zip_file_returns_nil + File.open('test/data/testDirectory.bin') do |f| + # cdir entry header is at least 46 bytes, so just read a bit. + fragment = f.read(12) + assert_nil(::Zip::Entry.read_c_dir_entry(StringIO.new(fragment))) + end end end diff --git a/test/local_entry_test.rb b/test/local_entry_test.rb index 9d2e3ed5..212c91af 100644 --- a/test/local_entry_test.rb +++ b/test/local_entry_test.rb @@ -42,16 +42,23 @@ def test_read_local_entry_from_non_zip_file end end - def test_read_local_entry_from_truncated_zip_file - fragment = '' - # local header is at least 30 bytes - ::File.open(TestZipFile::TEST_ZIP2.zip_name) { |f| fragment = f.read(12) } - - fragment.extend(IOizeString).reset - entry = ::Zip::Entry.new - entry.read_local_entry(fragment) - raise 'ZipError expected' - rescue ::Zip::Error + def test_read_local_entry_from_truncated_zip_file_raises_error + ::File.open(TestZipFile::TEST_ZIP2.zip_name) do |f| + # Local header is at least 30 bytes, so don't read it all here. + fragment = f.read(12) + assert_raises(::Zip::Error) do + entry = ::Zip::Entry.new + entry.read_local_entry(StringIO.new(fragment)) + end + end + end + + def test_read_local_entry_from_truncated_zip_file_returns_nil + ::File.open(TestZipFile::TEST_ZIP2.zip_name) do |f| + # Local header is at least 30 bytes, so don't read it all here. + fragment = f.read(12) + assert_nil(::Zip::Entry.read_local_entry(StringIO.new(fragment))) + end end def test_write_entry From afe1892208dcfefeb6c1c61d221d37e6aee7d314 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Fri, 18 Jun 2021 11:44:58 +0100 Subject: [PATCH 314/469] Fix a mis-firing CentralDirectory test. `test_read_from_truncated_zip_file` was not testing what it thought it was. It was testing whether we caught an out-of-bounds cdir offset, not whether we caught a corrupted cdir entry. This commit embraces the actual behaviour and tests that we catch an out-of-bounds error for both standard `IO`s and `StringIO`s. --- lib/zip/central_directory.rb | 7 ++++++- test/central_directory_test.rb | 25 ++++++++++++++++--------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/lib/zip/central_directory.rb b/lib/zip/central_directory.rb index d54e5670..e8c20cf2 100644 --- a/lib/zip/central_directory.rb +++ b/lib/zip/central_directory.rb @@ -149,11 +149,16 @@ def unpack_e_o_c_d(buffer) #:nodoc: end def read_central_directory_entries(io) #:nodoc: + # `StringIO` doesn't raise `EINVAL` if you seek beyond the current end, + # so we need to catch that *and* query `io#eof?` here. + eof = false begin io.seek(@cdir_offset, IO::SEEK_SET) rescue Errno::EINVAL - raise Error, 'Zip consistency problem while reading central directory entry' + eof = true end + raise Error, 'Zip consistency problem while reading central directory entry' if eof || io.eof? + @entry_set = EntrySet.new @size.times do entry = Entry.read_c_dir_entry(io) diff --git a/test/central_directory_test.rb b/test/central_directory_test.rb index 011bdb5f..afe2156e 100644 --- a/test/central_directory_test.rb +++ b/test/central_directory_test.rb @@ -26,15 +26,22 @@ def test_read_from_invalid_stream rescue ::Zip::Error end - def test_read_from_truncated_zip_file - fragment = '' - File.open('test/data/testDirectory.bin', 'rb') { |f| fragment = f.read } - fragment.slice!(12) # removed part of first cdir entry. eocd structure still complete - fragment.extend(IOizeString) - entry = ::Zip::CentralDirectory.new - entry.read_from_stream(fragment) - raise 'ZipError expected' - rescue ::Zip::Error + def test_read_eocd_with_wrong_cdir_offset_from_file + ::File.open('test/data/testDirectory.bin', 'rb') do |f| + assert_raises(::Zip::Error) do + cdir = ::Zip::CentralDirectory.new + cdir.read_from_stream(f) + end + end + end + + def test_read_eocd_with_wrong_cdir_offset_from_buffer + ::File.open('test/data/testDirectory.bin', 'rb') do |f| + assert_raises(::Zip::Error) do + cdir = ::Zip::CentralDirectory.new + cdir.read_from_stream(StringIO.new(f.read)) + end + end end def test_write_to_stream From 75386f8db6b85d5ace97fec3c9640a6499880c89 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Fri, 18 Jun 2021 11:50:07 +0100 Subject: [PATCH 315/469] Remove now redundant `IOizeString` module. --- test/test_helper.rb | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/test/test_helper.rb b/test/test_helper.rb index b51fd5d4..2896dd11 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -16,40 +16,6 @@ FileUtils.rm_rf('test/data/generated') end -module IOizeString - attr_reader :tell - - def read(count = nil) - @tell ||= 0 - count ||= size - ret_val = slice(@tell, count) - @tell += count - ret_val - end - - def seek(index, offset) - @tell ||= 0 - case offset - when IO::SEEK_END - pos = size + index - when IO::SEEK_SET - pos = index - when IO::SEEK_CUR - pos = @tell + index - else - raise 'Error in test method IOizeString::seek' - end - - raise Errno::EINVAL if pos < 0 || pos >= size - - @tell = pos - end - - def reset - @tell = 0 - end -end - module DecompressorTests # expects @ref_text, @ref_lines and @decompressor From 71f2c90b20e15bd27195ec4528b8e406fd0e2a26 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Fri, 18 Jun 2021 12:07:10 +0100 Subject: [PATCH 316/469] Test that a corrupted cdir entry is caught. --- lib/zip/entry.rb | 2 +- test/central_directory_entry_test.rb | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index cdff9baa..c317f468 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -381,7 +381,7 @@ def set_ftype_from_c_dir_entry end def check_c_dir_entry_static_header_length(buf) - return if buf.bytesize == ::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH + return unless buf.nil? || buf.bytesize != ::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH raise Error, 'Premature end of file. Not enough data for zip cdir entry header' end diff --git a/test/central_directory_entry_test.rb b/test/central_directory_entry_test.rb index e8af639c..76d8b305 100644 --- a/test/central_directory_entry_test.rb +++ b/test/central_directory_entry_test.rb @@ -77,4 +77,25 @@ def test_read_entry_from_truncated_zip_file_returns_nil assert_nil(::Zip::Entry.read_c_dir_entry(StringIO.new(fragment))) end end + + def test_read_corrupted_entry_raises_error + fragment = File.binread('test/data/testDirectory.bin') + fragment.slice!(12) + io = StringIO.new(fragment) + assert_raises(::Zip::Error) do + entry = ::Zip::Entry.new + entry.read_c_dir_entry(io) + # First entry will be read but break later entries. + entry.read_c_dir_entry(io) + end + end + + def test_read_corrupted_entry_returns_nil + fragment = File.binread('test/data/testDirectory.bin') + fragment.slice!(12) + io = StringIO.new(fragment) + refute_nil(::Zip::Entry.read_c_dir_entry(io)) + # First entry will be read but break later entries. + assert_nil(::Zip::Entry.read_c_dir_entry(io)) + end end From f66a15a85d09aa4216a51b6e58550c21d13ec4db Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Fri, 18 Jun 2021 16:00:57 +0100 Subject: [PATCH 317/469] Update rubocop config. --- .rubocop_todo.yml | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index e043313c..fd3243f3 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2021-05-24 20:07:32 UTC using RuboCop version 1.12.1. +# on 2021-06-18 14:28:03 UTC using RuboCop version 1.12.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -20,14 +20,14 @@ Lint/MissingSuper: # Offense count: 5 # Configuration parameters: CountComments, CountAsOne. Metrics/ClassLength: - Max: 610 + Max: 600 -# Offense count: 20 +# Offense count: 21 # Configuration parameters: IgnoredMethods. Metrics/CyclomaticComplexity: Max: 14 -# Offense count: 46 +# Offense count: 47 # Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods. Metrics/MethodLength: Max: 32 @@ -43,7 +43,7 @@ Metrics/ParameterLists: Metrics/PerceivedComplexity: Max: 15 -# Offense count: 8 +# Offense count: 7 Naming/AccessorMethodName: Exclude: - 'lib/zip/entry.rb' @@ -64,7 +64,7 @@ Style/ClassAndModuleChildren: - 'lib/zip/extra_field/zip64.rb' - 'lib/zip/extra_field/zip64_placeholder.rb' -# Offense count: 24 +# Offense count: 22 # Configuration parameters: AllowedConstants. Style/Documentation: Enabled: false @@ -74,7 +74,6 @@ Style/Documentation: Style/IfUnlessModifier: Exclude: - 'lib/zip/entry.rb' - - 'lib/zip/file.rb' - 'lib/zip/file_split.rb' - 'lib/zip/filesystem/dir.rb' - 'lib/zip/filesystem/file.rb' @@ -89,13 +88,12 @@ Style/MutableConstant: Exclude: - 'lib/zip/extra_field.rb' -# Offense count: 24 +# Offense count: 21 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, IgnoredMethods. # SupportedStyles: predicate, comparison Style/NumericPredicate: Exclude: - - 'spec/**/*' - 'lib/zip/entry.rb' - 'lib/zip/extra_field/old_unix.rb' - 'lib/zip/extra_field/universal_time.rb' @@ -105,7 +103,6 @@ Style/NumericPredicate: - 'lib/zip/input_stream.rb' - 'lib/zip/ioextras.rb' - 'lib/zip/ioextras/abstract_input_stream.rb' - - 'test/test_helper.rb' # Offense count: 6 # Configuration parameters: AllowedMethods. @@ -139,11 +136,10 @@ Style/SafeNavigation: - 'test/filesystem/file_stat_test.rb' - 'test/test_helper.rb' -# Offense count: 9 +# Offense count: 8 # Cop supports --auto-correct. Style/StringConcatenation: Exclude: - - 'lib/zip/filesystem.rb' - 'samples/gtk_ruby_zip.rb' - 'test/encryption_test.rb' - 'test/file_test.rb' From f1e8c2fc9d5a45e81d0e25c8eac119217bac488e Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Fri, 18 Jun 2021 16:10:57 +0100 Subject: [PATCH 318/469] Fix Style/StringConcatenation cop. --- .rubocop_todo.yml | 10 ---------- samples/gtk_ruby_zip.rb | 2 +- test/encryption_test.rb | 2 +- test/file_test.rb | 2 +- test/ioextras/abstract_input_stream_test.rb | 6 +++--- test/test_helper.rb | 4 ++-- 6 files changed, 8 insertions(+), 18 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index fd3243f3..d34c8bd1 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -135,13 +135,3 @@ Style/SafeNavigation: - 'test/filesystem/file_nonmutating_test.rb' - 'test/filesystem/file_stat_test.rb' - 'test/test_helper.rb' - -# Offense count: 8 -# Cop supports --auto-correct. -Style/StringConcatenation: - Exclude: - - 'samples/gtk_ruby_zip.rb' - - 'test/encryption_test.rb' - - 'test/file_test.rb' - - 'test/ioextras/abstract_input_stream_test.rb' - - 'test/test_helper.rb' diff --git a/samples/gtk_ruby_zip.rb b/samples/gtk_ruby_zip.rb index 674e8e77..eeac2a0a 100755 --- a/samples/gtk_ruby_zip.rb +++ b/samples/gtk_ruby_zip.rb @@ -74,7 +74,7 @@ def open_zip(filename) @zipfile.each do |entry| @clist.append([entry.name, entry.size.to_s, - (100.0 * entry.compressedSize / entry.size).to_s + '%']) + "#{100.0 * entry.compressedSize / entry.size}%"]) end end end diff --git a/test/encryption_test.rb b/test/encryption_test.rb index 110e7a3d..c1bc2038 100644 --- a/test/encryption_test.rb +++ b/test/encryption_test.rb @@ -33,7 +33,7 @@ def test_encrypt end assert_raises(Zip::DecompressionError) do - Zip::InputStream.open(encrypted_zip, 0, Zip::TraditionalDecrypter.new(password + 'wrong')) do |zis| + Zip::InputStream.open(encrypted_zip, 0, Zip::TraditionalDecrypter.new("#{password}wrong")) do |zis| zis.get_next_entry assert_equal content, zis.read end diff --git a/test/file_test.rb b/test/file_test.rb index a802f105..ec18aa2a 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -332,7 +332,7 @@ def test_add_directory ::Zip::File.open(TEST_ZIP.zip_name) do |zf| dir_entry = zf.entries.detect do |e| - e.name == TestFiles::EMPTY_TEST_DIR + '/' + e.name == "#{TestFiles::EMPTY_TEST_DIR}/" end assert(dir_entry.directory?) diff --git a/test/ioextras/abstract_input_stream_test.rb b/test/ioextras/abstract_input_stream_test.rb index 04b20990..2ef849c8 100644 --- a/test/ioextras/abstract_input_stream_test.rb +++ b/test/ioextras/abstract_input_stream_test.rb @@ -59,8 +59,8 @@ def test_gets_multi_char_seperator end LONG_LINES = [ - 'x' * 48 + "\r\n", - 'y' * 49 + "\r\n", + "#{'x' * 48}\r\n", + "#{'y' * 49}\r\n", 'rest' ].freeze @@ -74,7 +74,7 @@ def test_gets_mulit_char_seperator_split def test_gets_with_sep_and_index io = TestAbstractInputStream.new(LONG_LINES.join) assert_equal('x', io.gets("\r\n", 1)) - assert_equal('x' * 47 + "\r", io.gets("\r\n", 48)) + assert_equal("#{'x' * 47}\r", io.gets("\r\n", 48)) assert_equal("\n", io.gets(nil, 1)) assert_equal('yy', io.gets(nil, 2)) end diff --git a/test/test_helper.rb b/test/test_helper.rb index 2896dd11..c3ab2bb7 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -56,7 +56,7 @@ def assert_entry_contents_for_stream(filename, zis, entry_name) actual = zis.read if expected != actual if (expected && actual) && (expected.length > 400 || actual.length > 400) - entry_filename = entry_name + '.zipEntry' + entry_filename = "#{entry_name}.zipEntry" File.open(entry_filename, 'wb') { |entryfile| entryfile << actual } raise("File '#{filename}' is different from '#{entry_filename}'") else @@ -72,7 +72,7 @@ def self.assert_contents(filename, string) return unless contents != string if contents.length > 400 || string.length > 400 - string_file = filename + '.other' + string_file = "#{filename}.other" File.open(string_file, 'wb') { |f| f << string } raise("File '#{filename}' is different from contents of string stored in '#{string_file}'") else From a2a14c2cd2d23628bb81a8f09384d3e51f040e8b Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Fri, 18 Jun 2021 16:31:23 +0100 Subject: [PATCH 319/469] Fix Style/RedundantRegexpEscape cop. --- .rubocop_todo.yml | 8 -------- Guardfile | 2 +- test/file_extract_test.rb | 2 +- test/path_traversal_test.rb | 26 +++++++++++++------------- 4 files changed, 15 insertions(+), 23 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index d34c8bd1..b86c4133 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -114,14 +114,6 @@ Style/OptionalBooleanParameter: - 'lib/zip/file_split.rb' - 'lib/zip/output_stream.rb' -# Offense count: 29 -# Cop supports --auto-correct. -Style/RedundantRegexpEscape: - Exclude: - - 'Guardfile' - - 'test/file_extract_test.rb' - - 'test/path_traversal_test.rb' - # Offense count: 17 # Cop supports --auto-correct. # Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods. diff --git a/Guardfile b/Guardfile index 5365a7a5..bf0c0f89 100644 --- a/Guardfile +++ b/Guardfile @@ -2,7 +2,7 @@ guard :minitest do # with Minitest::Unit - watch(%r{^test/(.*)\/?(.*)_test\.rb$}) + watch(%r{^test/(.*)/?(.*)_test\.rb$}) watch(%r{^lib/zip/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}#{m[2]}_test.rb" } watch(%r{^test/test_helper\.rb$}) { 'test' } end diff --git a/test/file_extract_test.rb b/test/file_extract_test.rb index 3df4308b..9d0b7da4 100644 --- a/test/file_extract_test.rb +++ b/test/file_extract_test.rb @@ -129,7 +129,7 @@ def test_extract_incorrect_size assert_equal fake_size, a_entry.size ::Zip.validate_entry_sizes = false - assert_output('', /.+\'a\'.+1B.+/) do + assert_output('', /.+'a'.+1B.+/) do a_entry.extract end assert_equal true_size, File.size(file_name) diff --git a/test/path_traversal_test.rb b/test/path_traversal_test.rb index 9676432b..4b500191 100644 --- a/test/path_traversal_test.rb +++ b/test/path_traversal_test.rb @@ -39,7 +39,7 @@ def in_tmpdir end def test_leading_slash - entries = { '/tmp/moo' => /WARNING: skipped \'\/tmp\/moo\'/ } + entries = { '/tmp/moo' => /WARNING: skipped '\/tmp\/moo'/ } in_tmpdir do extract_paths(['jwilk', 'absolute1.zip'], entries) refute File.exist?('/tmp/moo') @@ -47,7 +47,7 @@ def test_leading_slash end def test_multiple_leading_slashes - entries = { '//tmp/moo' => /WARNING: skipped \'\/\/tmp\/moo\'/ } + entries = { '//tmp/moo' => /WARNING: skipped '\/\/tmp\/moo'/ } in_tmpdir do extract_paths(['jwilk', 'absolute2.zip'], entries) refute File.exist?('/tmp/moo') @@ -55,7 +55,7 @@ def test_multiple_leading_slashes end def test_leading_dot_dot - entries = { '../moo' => /WARNING: skipped \'\.\.\/moo\'/ } + entries = { '../moo' => /WARNING: skipped '\.\.\/moo'/ } in_tmpdir do extract_paths(['jwilk', 'relative0.zip'], entries) refute File.exist?('../moo') @@ -65,7 +65,7 @@ def test_leading_dot_dot def test_non_leading_dot_dot_with_existing_folder entries = { 'tmp/' => '', - 'tmp/../../moo' => /WARNING: skipped \'tmp\/\.\.\/\.\.\/moo\'/ + 'tmp/../../moo' => /WARNING: skipped 'tmp\/\.\.\/\.\.\/moo'/ } in_tmpdir do extract_paths('relative1.zip', entries) @@ -75,7 +75,7 @@ def test_non_leading_dot_dot_with_existing_folder end def test_non_leading_dot_dot_without_existing_folder - entries = { 'tmp/../../moo' => /WARNING: skipped \'tmp\/\.\.\/\.\.\/moo\'/ } + entries = { 'tmp/../../moo' => /WARNING: skipped 'tmp\/\.\.\/\.\.\/moo'/ } in_tmpdir do extract_paths(['jwilk', 'relative2.zip'], entries) refute File.exist?('../moo') @@ -94,7 +94,7 @@ def test_file_symlink def test_directory_symlink # Can't create tmp/moo, because the tmp symlink is skipped. entries = { - 'tmp' => /WARNING: skipped symlink \'tmp\'/, + 'tmp' => /WARNING: skipped symlink 'tmp'/, 'tmp/moo' => :error } in_tmpdir do @@ -106,8 +106,8 @@ def test_directory_symlink def test_two_directory_symlinks_a # Can't create par/moo because the symlinks are skipped. entries = { - 'cur' => /WARNING: skipped symlink \'cur\'/, - 'par' => /WARNING: skipped symlink \'par\'/, + 'cur' => /WARNING: skipped symlink 'cur'/, + 'par' => /WARNING: skipped symlink 'par'/, 'par/moo' => :error } in_tmpdir do @@ -121,8 +121,8 @@ def test_two_directory_symlinks_a def test_two_directory_symlinks_b # Can't create par/moo, because the symlinks are skipped. entries = { - 'cur' => /WARNING: skipped symlink \'cur\'/, - 'cur/par' => /WARNING: skipped symlink \'cur\/par\'/, + 'cur' => /WARNING: skipped symlink 'cur'/, + 'cur/par' => /WARNING: skipped symlink 'cur\/par'/, 'par/moo' => :error } in_tmpdir do @@ -134,8 +134,8 @@ def test_two_directory_symlinks_b def test_entry_name_with_absolute_path_does_not_extract entries = { - '/tmp/' => /WARNING: skipped \'\/tmp\/\'/, - '/tmp/file.txt' => /WARNING: skipped \'\/tmp\/file.txt\'/ + '/tmp/' => /WARNING: skipped '\/tmp\/'/, + '/tmp/file.txt' => /WARNING: skipped '\/tmp\/file.txt'/ } in_tmpdir do extract_paths(['tuzovakaoff', 'absolutepath.zip'], entries) @@ -158,7 +158,7 @@ def test_entry_name_with_absolute_path_extract_when_given_different_path def test_entry_name_with_relative_symlink # Doesn't create the symlink path, so can't create path/file.txt. entries = { - 'path' => /WARNING: skipped symlink \'path\'/, + 'path' => /WARNING: skipped symlink 'path'/, 'path/file.txt' => :error } in_tmpdir do From 1183607ea12349962eef4a88c91ab471048500a9 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 23 Jun 2021 22:24:44 +0100 Subject: [PATCH 320/469] Flush buffered `OutputStream` on close. Fixes #265. --- lib/zip/output_stream.rb | 1 + test/output_stream_test.rb | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/lib/zip/output_stream.rb b/lib/zip/output_stream.rb index 5d13ebad..3a034ed5 100644 --- a/lib/zip/output_stream.rb +++ b/lib/zip/output_stream.rb @@ -86,6 +86,7 @@ def close_buffer update_local_headers write_central_directory @closed = true + @output_stream.flush @output_stream end diff --git a/test/output_stream_test.rb b/test/output_stream_test.rb index 7a994b36..ecd48488 100644 --- a/test/output_stream_test.rb +++ b/test/output_stream_test.rb @@ -58,6 +58,20 @@ def test_write_buffer_with_temp_file assert_test_zip_contents(TEST_ZIP) end + def test_write_buffer_with_temp_file2 + tmp_file = ::File.join(Dir.tmpdir, 'zos.zip') + ::File.open(tmp_file, 'wb') do |f| + ::Zip::OutputStream.write_buffer(f) do |zos| + zos.comment = TEST_ZIP.comment + write_test_zip(zos) + end + end + + ::Zip::File.open(tmp_file) # Should open without error. + ensure + ::File.unlink(tmp_file) + end + def test_writing_to_closed_stream assert_i_o_error_in_closed_stream { |zos| zos << 'hello world' } assert_i_o_error_in_closed_stream { |zos| zos.puts 'hello world' } From ac053bd787b69d90ff8ed23fd800c0063c15a7d4 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Fri, 25 Jun 2021 16:37:40 +0100 Subject: [PATCH 321/469] Improve documentation and error messages for `InputStream`. Closes #196. --- README.md | 22 +++++++++++++++++----- lib/zip/inflater.rb | 5 +++-- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 52ba31f3..d70e2881 100644 --- a/README.md +++ b/README.md @@ -174,15 +174,27 @@ Zip::File.open('foo.zip') do |zip_file| end ``` -#### Notice about ::Zip::InputStream +### Notes on `Zip::InputStream` -`::Zip::InputStream` usable for fast reading zip file content because it not read Central directory. +`Zip::InputStream` can be used for faster reading of zip file content because it does not read the Central directory up front. -But there is one exception when it is not working - General Purpose Flag Bit 3. +There is one exception where it can not work however, and this is if the file does not contain enough information in the local entry headers to extract an entry. This is indicated in an entry by the General Purpose Flag bit 3 being set. -> If bit 3 (0x08) of the general-purpose flags field is set, then the CRC-32 and file sizes are not known when the header is written. The fields in the local header are filled with zero, and the CRC-32 and size are appended in a 12-byte structure (optionally preceded by a 4-byte signature) immediately after the compressed data +> If bit 3 (0x08) of the general-purpose flags field is set, then the CRC-32 and file sizes are not known when the header is written. The fields in the local header are filled with zero, and the CRC-32 and size are appended in a 12-byte structure (optionally preceded by a 4-byte signature) immediately after the compressed data. -If `::Zip::InputStream` finds such entry in the zip archive it will raise an exception. +If `Zip::InputStream` finds such an entry in the zip archive it will raise an exception (`Zip::GPFBit3Error`). + +`Zip::InputStream` is not designed to be used for random access in a zip file. When performing any operations on an entry that you are accessing via `Zip::InputStream.get_next_entry` then you should complete any such operations before the next call to `get_next_entry`. + +```ruby +zip_stream = Zip::InputStream.new(File.open('file.zip')) + +while entry = zip_stream.get_next_entry + # All required operations on `entry` go here. +end +``` + +Any attempt to move about in a zip file opened with `Zip::InputStream` could result in the incorrect entry being accessed and/or Zlib buffer errors. If you need random access in a zip file, use `Zip::File`. ### Password Protection (Experimental) diff --git a/lib/zip/inflater.rb b/lib/zip/inflater.rb index 8f686f3e..c702da75 100644 --- a/lib/zip/inflater.rb +++ b/lib/zip/inflater.rb @@ -39,8 +39,9 @@ def produce_input retried += 1 retry end - rescue Zlib::Error - raise(::Zip::DecompressionError, 'zlib error while inflating') + rescue Zlib::Error => e + raise ::Zip::DecompressionError, + "Zlib error ('#{e.message}') while inflating" end def input_finished? From c29297c0b8a6a7f3e2441206b295cd82117cdfc9 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Fri, 25 Jun 2021 17:40:46 +0100 Subject: [PATCH 322/469] Add a test to ensure `InputStream` raises `GPFBit3Error`. --- test/input_stream_test.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/input_stream_test.rb b/test/input_stream_test.rb index 40d19476..8a74d149 100644 --- a/test/input_stream_test.rb +++ b/test/input_stream_test.rb @@ -67,6 +67,14 @@ def test_open_io_like_with_block end end + def test_open_file_with_gp3bit_set + ::Zip::InputStream.open('test/data/gpbit3stored.zip') do |zis| + assert_raises(::Zip::GPFBit3Error) do + zis.get_next_entry + end + end + end + def test_size_no_entry zis = ::Zip::InputStream.open(TestZipFile::TEST_ZIP2.zip_name) assert_nil(zis.size) From 78565db40c60ed4e95e6cc37bf470a4194947a71 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Fri, 25 Jun 2021 17:43:53 +0100 Subject: [PATCH 323/469] Simplify `InputStream.open_entry`. Also ensure `@complete_entry` is initialized! --- lib/zip/input_stream.rb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index 3dce2fcc..19be8a40 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -53,10 +53,11 @@ class InputStream # @param offset [Integer] offset in the IO/StringIO def initialize(context, offset = 0, decrypter = nil) super() - @archive_io = get_io(context, offset) - @decompressor = ::Zip::NullDecompressor - @decrypter = decrypter || ::Zip::NullDecrypter.new + @archive_io = get_io(context, offset) + @decompressor = ::Zip::NullDecompressor + @decrypter = decrypter || ::Zip::NullDecrypter.new @current_entry = nil + @complete_entry = nil end def close @@ -131,17 +132,18 @@ def get_io(io_or_file, offset = 0) def open_entry @current_entry = ::Zip::Entry.read_local_entry(@archive_io) - if @current_entry && @current_entry.encrypted? && @decrypter.kind_of?(NullEncrypter) - raise Error, 'password required to decode zip file' - end + return if @current_entry.nil? - if @current_entry && @current_entry.incomplete? && @current_entry.crc == 0 \ + raise Error, 'A password is required to decode this zip file' if @current_entry.encrypted? && @decrypter.kind_of?(NullEncrypter) + + if @current_entry.incomplete? && @current_entry.crc == 0 \ && @current_entry.compressed_size == 0 \ && @current_entry.size == 0 && !@complete_entry raise GPFBit3Error, 'General purpose flag Bit 3 is set so not possible to get proper info from local header.' \ 'Please use ::Zip::File instead of ::Zip::InputStream' end + @decrypted_io = get_decrypted_io @decompressor = get_decompressor flush From 84b3e8c644e553d6baa7546acc9223080e2aca00 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Fri, 25 Jun 2021 17:51:54 +0100 Subject: [PATCH 324/469] Ensure `InputStream` raises `GPFBit3Error` for OSX Archive files. Fixes #493. --- lib/zip/input_stream.rb | 10 +++++----- test/data/osx-archive.zip | Bin 0 -> 1424 bytes test/input_stream_test.rb | 8 ++++++++ 3 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 test/data/osx-archive.zip diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index 19be8a40..d7985492 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -136,12 +136,12 @@ def open_entry raise Error, 'A password is required to decode this zip file' if @current_entry.encrypted? && @decrypter.kind_of?(NullEncrypter) - if @current_entry.incomplete? && @current_entry.crc == 0 \ - && @current_entry.compressed_size == 0 \ - && @current_entry.size == 0 && !@complete_entry + if @current_entry.incomplete? && @current_entry.compressed_size == 0 \ + && !@complete_entry raise GPFBit3Error, - 'General purpose flag Bit 3 is set so not possible to get proper info from local header.' \ - 'Please use ::Zip::File instead of ::Zip::InputStream' + 'It is not possible to get complete info from the local ' \ + 'header to extract this entry (GP flags bit 3 is set).' \ + 'Please use `Zip::File` instead of `Zip::InputStream`.' end @decrypted_io = get_decrypted_io diff --git a/test/data/osx-archive.zip b/test/data/osx-archive.zip new file mode 100644 index 0000000000000000000000000000000000000000..147a743f2ebc3c80ffa8ffdd2a69d18452a9f6ed GIT binary patch literal 1424 zcmWIWW@Zs#-~hq&3qcG}FoltUft5jl!BDTHq9ingmx2Akvy%xR{0WFlE4UdLS-t_) z0JSnO)b^jwI%2@%`u$(m>`3*$?LoUFG?sKXF7#d+D0gLJk38qU-?#iUW4B!O@^Eio zef!W0eqJMGr+4v-zpt!bw7|-ypM9sGzK`F^)j`|iZoZ;-*BmgkSE~sJX73Mv1ErEA6)4G|kjF3xC5KT@`NJ{wN>l5~YKdd95foXySv$}vZ z{scw75j^rA10x-TKVwFIYoM<;v!TG==iz(4-TBI1A*k)d?Nl8h(7$-5T4M?Oq&0{C zRj1pp%kn$3yGuN4-Qm^YT5|b5M|Xa{xwF+}X-ZV&lQi3e z|BA{M#S0cbRGU@KXVLjcJ80)}#S~-B;=?J2vJRY!o$%Hstj6eFh*#47%>jbLzY@V|0u$%w2`knFzrL!XcM%kTT z;HI}nrr&JmYi?sF!L{!yYlV_8C&Y=)R9>gqvhuP>z}j`&w|u|y=o-_t{Jx~kr+5uR zd4ekE?Oc2L{)PXM{D0Tme`5v(X4Ye?*S~-P2?{dofr%w)V210UOd5<#BFu=A0XY+Z z5(f+{X#}w_Q#+P&BETEfNaU0V$~`c!q){IjoTTO@WYa;Z898cD9sCz)I%XVTN!#c~ g;&!kXGm3*TO~(?$0p6@^ATP53VG__u5-cDd0PvXm!2kdN literal 0 HcmV?d00001 diff --git a/test/input_stream_test.rb b/test/input_stream_test.rb index 8a74d149..4f8f5d29 100644 --- a/test/input_stream_test.rb +++ b/test/input_stream_test.rb @@ -75,6 +75,14 @@ def test_open_file_with_gp3bit_set end end + def test_open_file_with_gp3bit_set_created_by_osx_archive + ::Zip::InputStream.open('test/data/osx-archive.zip') do |zis| + assert_raises(::Zip::GPFBit3Error) do + zis.get_next_entry + end + end + end + def test_size_no_entry zis = ::Zip::InputStream.open(TestZipFile::TEST_ZIP2.zip_name) assert_nil(zis.size) From de6ec15610ce47e044284ff86da8e5f38b276e2e Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Thu, 24 Jun 2021 17:59:54 +0100 Subject: [PATCH 325/469] Update Changelog. --- Changelog.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Changelog.md b/Changelog.md index 4036881d..09968a8e 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,10 @@ # 3.0.0 (Next) +- Ensure `InputStream` raises `GPFBit3Error` for OSX Archive files. [#493](https://github.com/rubyzip/rubyzip/issues/493) +- Improve documentation and error messages for `InputStream`. [#196](https://github.com/rubyzip/rubyzip/issues/196) +- Fix zip file-level comment is not read from zip64 files. [#492](https://github.com/rubyzip/rubyzip/issues/492) +- Fix `Zip::OutputStream.write_buffer` doesn't work with Tempfiles. [#265](https://github.com/rubyzip/rubyzip/issues/265) +- Reinstate normalising pathname separators to /. [#487](https://github.com/rubyzip/rubyzip/pull/487) - Fix restore options consistency. [#486](https://github.com/rubyzip/rubyzip/pull/486) - View and/or preserve original date created, date modified? (Windows). [#336](https://github.com/rubyzip/rubyzip/issues/336) - Fix frozen string literal error. [#475](https://github.com/rubyzip/rubyzip/pull/475) @@ -14,6 +19,7 @@ Tooling/internal: +- Configure Coveralls to not report a failure on minor decreases of test coverage. [#491](https://github.com/rubyzip/rubyzip/issues/491) - Extract the file splitting code out into its own module. - Refactor, and tidy up, the `Zip::Filesystem` classes for improved maintainability. - Fix Windows tests. [#489](https://github.com/rubyzip/rubyzip/pull/489) From 193507b15ada37bc74df3d3fb6a6e064be635bdf Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Fri, 25 Jun 2021 22:31:34 +0100 Subject: [PATCH 326/469] Adjust Layout/LineLength cop to 100 characters. We'll get the line length down in stages... --- .rubocop.yml | 2 +- lib/zip/central_directory.rb | 3 ++- lib/zip/entry.rb | 29 ++++++++++++++++------- lib/zip/extra_field/zip64.rb | 7 ++++-- lib/zip/file.rb | 3 ++- lib/zip/file_split.rb | 17 +++++++++---- lib/zip/input_stream.rb | 12 +++++++--- lib/zip/ioextras/abstract_input_stream.rb | 5 +++- lib/zip/output_stream.rb | 6 ++++- samples/example.rb | 10 +++++--- 10 files changed, 68 insertions(+), 26 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 7f1fc683..d3db8664 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -22,7 +22,7 @@ Layout/HashAlignment: # Set a workable line length, given the current state of the code, # and turn off for the tests. Layout/LineLength: - Max: 135 + Max: 100 Exclude: - 'test/**/*.rb' diff --git a/lib/zip/central_directory.rb b/lib/zip/central_directory.rb index e8c20cf2..51d72b37 100644 --- a/lib/zip/central_directory.rb +++ b/lib/zip/central_directory.rb @@ -30,7 +30,8 @@ def write_to_stream(io) #:nodoc: eocd_offset = io.tell cdir_size = eocd_offset - cdir_offset if ::Zip.write_zip64_support - need_zip64_eocd = cdir_offset > 0xFFFFFFFF || cdir_size > 0xFFFFFFFF || @entry_set.size > 0xFFFF + need_zip64_eocd = cdir_offset > 0xFFFFFFFF || cdir_size > 0xFFFFFFFF \ + || @entry_set.size > 0xFFFF need_zip64_eocd ||= @entry_set.any? { |entry| entry.extra['Zip64'] } if need_zip64_eocd write_64_e_o_c_d(io, cdir_offset, cdir_size) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index c317f468..5b95d787 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -195,7 +195,10 @@ def verify_local_header_size! return if @local_header_size.nil? new_size = calculate_local_header_size - raise Error, "local header size changed (#{@local_header_size} -> #{new_size})" if @local_header_size != new_size + return unless @local_header_size != new_size + + raise Error, + "Local header size changed (#{@local_header_size} -> #{new_size})" end def cdir_header_size #:nodoc:all @@ -363,8 +366,9 @@ def set_ftype_from_c_dir_entry when ::Zip::FILE_TYPE_SYMLINK :symlink else - # best case guess for whether it is a file or not - # Otherwise this would be set to unknown and that entry would never be able to extracted + # Best case guess for whether it is a file or not. + # Otherwise this would be set to unknown and that + # entry would never be able to be extracted. if name_is_directory? :directory else @@ -444,13 +448,18 @@ def get_extra_attributes_from_path(path) # :nodoc: @unix_perms = stat.mode & 0o7777 end + # rubocop:disable Style/GuardClause def set_unix_attributes_on_path(dest_path) - # ignore setuid/setgid bits by default. honor if @restore_ownership - unix_perms_mask = 0o1777 - unix_perms_mask = 0o7777 if @restore_ownership - ::FileUtils.chmod(@unix_perms & unix_perms_mask, dest_path) if @restore_permissions && @unix_perms - ::FileUtils.chown(@unix_uid, @unix_gid, dest_path) if @restore_ownership && @unix_uid && @unix_gid && ::Process.egid == 0 + # Ignore setuid/setgid bits by default. Honour if @restore_ownership. + unix_perms_mask = (@restore_ownership ? 0o7777 : 0o1777) + if @restore_permissions && @unix_perms + ::FileUtils.chmod(@unix_perms & unix_perms_mask, dest_path) + end + if @restore_ownership && @unix_uid && @unix_gid && ::Process.egid == 0 + ::FileUtils.chown(@unix_uid, @unix_gid, dest_path) + end end + # rubocop:enable Style/GuardClause def set_extra_attributes_on_path(dest_path) # :nodoc: return unless file? || directory? @@ -693,7 +702,9 @@ def parse_zip64_extra(for_local_header) #:nodoc:all if for_local_header @size, @compressed_size = @extra['Zip64'].parse(@size, @compressed_size) else - @size, @compressed_size, @local_header_offset = @extra['Zip64'].parse(@size, @compressed_size, @local_header_offset) + @size, @compressed_size, @local_header_offset = @extra['Zip64'].parse( + @size, @compressed_size, @local_header_offset + ) end end diff --git a/lib/zip/extra_field/zip64.rb b/lib/zip/extra_field/zip64.rb index 0e73ce0d..ba7b5110 100644 --- a/lib/zip/extra_field/zip64.rb +++ b/lib/zip/extra_field/zip64.rb @@ -40,7 +40,9 @@ def merge(binstr) def parse(original_size, compressed_size, relative_header_offset = nil, disk_start_number = nil) @original_size = extract(8, 'Q<') if original_size == 0xFFFFFFFF @compressed_size = extract(8, 'Q<') if compressed_size == 0xFFFFFFFF - @relative_header_offset = extract(8, 'Q<') if relative_header_offset && relative_header_offset == 0xFFFFFFFF + if relative_header_offset && relative_header_offset == 0xFFFFFFFF + @relative_header_offset = extract(8, 'Q<') + end @disk_start_number = extract(4, 'V') if disk_start_number && disk_start_number == 0xFFFF @content = nil [@original_size || original_size, @@ -55,7 +57,8 @@ def extract(size, format) private :extract def pack_for_local - # local header entries must contain original size and compressed size; other fields do not apply + # Local header entries must contain original size and compressed size; + # other fields do not apply. return '' unless @original_size && @compressed_size [@original_size, @compressed_size].pack('Q segment_size szip_file_name = "#{partial_zip_file_name}.#{format('%03d', szip_file_index)}" @@ -52,7 +55,7 @@ def save_splited_part(zip_file, partial_zip_file_name, zip_file_size, szip_file_ chunk_bytes = 0 until ssegment_size == chunk_bytes || zip_file.eof? segment_bytes_left = ssegment_size - chunk_bytes - buffer_size = segment_bytes_left < DATA_BUFFER_SIZE ? segment_bytes_left : DATA_BUFFER_SIZE + buffer_size = [segment_bytes_left, DATA_BUFFER_SIZE].min chunk = zip_file.read(buffer_size) chunk_bytes += buffer_size szip_file << chunk @@ -63,7 +66,10 @@ def save_splited_part(zip_file, partial_zip_file_name, zip_file_size, szip_file_ end # Splits an archive into parts with segment size - 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_zip_file = true, partial_zip_file_name = nil + ) raise Error, "File #{zip_file_name} not found" unless ::File.exist?(zip_file_name) raise Errno::ENOENT, zip_file_name unless ::File.readable?(zip_file_name) @@ -78,7 +84,10 @@ def split(zip_file_name, segment_size = MAX_SEGMENT_SIZE, delete_zip_file = true ::File.open(zip_file_name, 'rb') do |zip_file| until zip_file.eof? szip_file_index += 1 - save_splited_part(zip_file, partial_zip_file_name, zip_file_size, szip_file_index, segment_size, segment_count) + save_splited_part( + zip_file, partial_zip_file_name, zip_file_size, + szip_file_index, segment_size, segment_count + ) end end ::File.delete(zip_file_name) if delete_zip_file diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index d7985492..1eb4080f 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -134,7 +134,10 @@ def open_entry @current_entry = ::Zip::Entry.read_local_entry(@archive_io) return if @current_entry.nil? - raise Error, 'A password is required to decode this zip file' if @current_entry.encrypted? && @decrypter.kind_of?(NullEncrypter) + if @current_entry.encrypted? && @decrypter.kind_of?(NullEncrypter) + raise Error, + 'A password is required to decode this zip file' + end if @current_entry.incomplete? && @current_entry.compressed_size == 0 \ && !@complete_entry @@ -161,13 +164,16 @@ def get_decompressor return ::Zip::NullDecompressor if @current_entry.nil? decompressed_size = - if @current_entry.incomplete? && @current_entry.crc == 0 && @current_entry.size == 0 && @complete_entry + if @current_entry.incomplete? && @current_entry.crc == 0 \ + && @current_entry.size == 0 && @complete_entry @complete_entry.size else @current_entry.size end - decompressor_class = ::Zip::Decompressor.find_by_compression_method(@current_entry.compression_method) + decompressor_class = ::Zip::Decompressor.find_by_compression_method( + @current_entry.compression_method + ) if decompressor_class.nil? raise ::Zip::CompressionMethodError, "Unsupported compression method #{@current_entry.compression_method}" diff --git a/lib/zip/ioextras/abstract_input_stream.rb b/lib/zip/ioextras/abstract_input_stream.rb index 11438fc7..d0ea6825 100644 --- a/lib/zip/ioextras/abstract_input_stream.rb +++ b/lib/zip/ioextras/abstract_input_stream.rb @@ -84,7 +84,10 @@ def gets(a_sep_string = $INPUT_RECORD_SEPARATOR, number_of_bytes = nil) @output_buffer << produce_input over_limit = (number_of_bytes && @output_buffer.bytesize >= number_of_bytes) end - sep_index = [match_index + a_sep_string.bytesize, number_of_bytes || @output_buffer.bytesize].min + sep_index = [ + match_index + a_sep_string.bytesize, + number_of_bytes || @output_buffer.bytesize + ].min @pos += sep_index @output_buffer.slice!(0...sep_index) end diff --git a/lib/zip/output_stream.rb b/lib/zip/output_stream.rb index 3a034ed5..a648a3d0 100644 --- a/lib/zip/output_stream.rb +++ b/lib/zip/output_stream.rb @@ -142,7 +142,11 @@ def finalize_current_entry @current_entry.calculate_local_header_size @current_entry.size = @compressor.size @current_entry.crc = @compressor.crc - @output_stream << @encrypter.data_descriptor(@current_entry.crc, @current_entry.compressed_size, @current_entry.size) + @output_stream << @encrypter.data_descriptor( + @current_entry.crc, + @current_entry.compressed_size, + @current_entry.size + ) @current_entry.gp_flags |= @encrypter.gp_flags @current_entry = nil @compressor = ::Zip::NullCompressor.instance diff --git a/samples/example.rb b/samples/example.rb index 3e82d0a2..713729fb 100755 --- a/samples/example.rb +++ b/samples/example.rb @@ -21,7 +21,8 @@ zf = Zip::File.new('example.zip') zf.each_with_index do |entry, index| - puts "entry #{index} is #{entry.name}, size = #{entry.size}, compressed size = #{entry.compressed_size}" + puts "entry #{index} is #{entry.name}, size = #{entry.size}, " \ + "compressed size = #{entry.compressed_size}" # use zf.get_input_stream(entry) to get a ZipInputStream for the entry # entry can be the ZipEntry object or any object which has a to_s method that # returns the name of the entry. @@ -71,8 +72,11 @@ puts "Zip file splitted in #{part_zips_count} parts" # Track splitting an archive -Zip::File.split('large_zip_file.zip', 1_048_576, true, 'part_zip_file') do |part_count, part_index, chunk_bytes, segment_bytes| - puts "#{part_index} of #{part_count} part splitting: #{(chunk_bytes.to_f / segment_bytes * 100).to_i}%" +Zip::File.split( + 'large_zip_file.zip', 1_048_576, true, 'part_zip_file' +) do |part_count, part_index, chunk_bytes, segment_bytes| + puts "#{part_index} of #{part_count} part splitting: " \ + "#{(chunk_bytes.to_f / segment_bytes * 100).to_i}%" end # For other examples, look at zip.rb and ziptest.rb From e000552deb661bf915b49b7b13c0c41029884e2c Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 26 Jun 2021 11:26:06 +0100 Subject: [PATCH 327/469] Raise an error on reading a split archive with `InputStream`. Fixes #349. --- .rubocop_todo.yml | 2 +- Changelog.md | 1 + lib/zip/entry.rb | 11 +++++++++-- lib/zip/errors.rb | 1 + test/data/invalid-split.zip | Bin 0 -> 180 bytes test/input_stream_test.rb | 8 ++++++++ 6 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 test/data/invalid-split.zip diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b86c4133..84dcc795 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -20,7 +20,7 @@ Lint/MissingSuper: # Offense count: 5 # Configuration parameters: CountComments, CountAsOne. Metrics/ClassLength: - Max: 600 + Max: 610 # Offense count: 21 # Configuration parameters: IgnoredMethods. diff --git a/Changelog.md b/Changelog.md index 09968a8e..26143099 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,6 @@ # 3.0.0 (Next) +- Raise an error on reading a split archive with `InputStream`. [#349](https://github.com/rubyzip/rubyzip/issues/349) - Ensure `InputStream` raises `GPFBit3Error` for OSX Archive files. [#493](https://github.com/rubyzip/rubyzip/issues/493) - Improve documentation and error messages for `InputStream`. [#196](https://github.com/rubyzip/rubyzip/issues/196) - Fix zip file-level comment is not read from zip64 files. [#492](https://github.com/rubyzip/rubyzip/issues/492) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 5b95d787..8b0c9bc4 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -250,6 +250,8 @@ def read_local_entry(io) entry = new(io) entry.read_local_entry(io) entry + rescue SplitArchiveError + raise rescue Error nil end @@ -281,8 +283,13 @@ def read_local_entry(io) #:nodoc:all unpack_local_entry(static_sized_fields_buf) - unless @header_signature == ::Zip::LOCAL_ENTRY_SIGNATURE - raise ::Zip::Error, "Zip local header magic not found at location '#{local_header_offset}'" + unless @header_signature == LOCAL_ENTRY_SIGNATURE + if @header_signature == SPLIT_FILE_SIGNATURE + raise SplitArchiveError, + 'Rubyzip cannot extract from split archives at this time' + end + + raise Error, "Zip local header magic not found at location '#{local_header_offset}'" end set_time(@last_mod_date, @last_mod_time) diff --git a/lib/zip/errors.rb b/lib/zip/errors.rb index 947f8dc4..ca15e2be 100644 --- a/lib/zip/errors.rb +++ b/lib/zip/errors.rb @@ -10,6 +10,7 @@ class EntrySizeError < Error; end class InternalError < Error; end class GPFBit3Error < Error; end class DecompressionError < Error; end + class SplitArchiveError < Error; end # Backwards compatibility with v1 (delete in v2) ZipError = Error diff --git a/test/data/invalid-split.zip b/test/data/invalid-split.zip new file mode 100644 index 0000000000000000000000000000000000000000..e6323d9ee771b0413ff0e596481cfca19b4319e9 GIT binary patch literal 180 zcmWIWX6Fd-W@Zs#U|`^2FzsvizH0O94i}Ke4;GPOC`m0Y(JQGa2@T<7U}g)|jM4z& z(h6<{MwYLP3=CkM+Mc?4ff|j1F$@9Tj7)OOxJ;7(n#sTb)Wxu*kp)O1%w~m{jb?6u SH!B-N2P1 Date: Sat, 26 Jun 2021 17:37:09 +0100 Subject: [PATCH 328/469] Remove the `ZipXError` v1 legacy classes. --- Changelog.md | 1 + lib/zip/errors.rb | 8 -------- test/errors_test.rb | 35 ----------------------------------- 3 files changed, 1 insertion(+), 43 deletions(-) delete mode 100644 test/errors_test.rb diff --git a/Changelog.md b/Changelog.md index 26143099..123a9f03 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,6 @@ # 3.0.0 (Next) +- Remove the `ZipXError` v1 legacy classes. - Raise an error on reading a split archive with `InputStream`. [#349](https://github.com/rubyzip/rubyzip/issues/349) - Ensure `InputStream` raises `GPFBit3Error` for OSX Archive files. [#493](https://github.com/rubyzip/rubyzip/issues/493) - Improve documentation and error messages for `InputStream`. [#196](https://github.com/rubyzip/rubyzip/issues/196) diff --git a/lib/zip/errors.rb b/lib/zip/errors.rb index ca15e2be..66ae5356 100644 --- a/lib/zip/errors.rb +++ b/lib/zip/errors.rb @@ -11,12 +11,4 @@ class InternalError < Error; end class GPFBit3Error < Error; end class DecompressionError < Error; end class SplitArchiveError < Error; end - - # Backwards compatibility with v1 (delete in v2) - ZipError = Error - ZipEntryExistsError = EntryExistsError - ZipDestinationFileExistsError = DestinationFileExistsError - ZipCompressionMethodError = CompressionMethodError - ZipEntryNameError = EntryNameError - ZipInternalError = InternalError end diff --git a/test/errors_test.rb b/test/errors_test.rb deleted file mode 100644 index 2f1b506d..00000000 --- a/test/errors_test.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -require 'test_helper' - -class ErrorsTest < MiniTest::Test - def test_rescue_legacy_zip_error - raise ::Zip::Error - rescue ::Zip::ZipError - end - - def test_rescue_legacy_zip_entry_exists_error - raise ::Zip::EntryExistsError - rescue ::Zip::ZipEntryExistsError - end - - def test_rescue_legacy_zip_destination_file_exists_error - raise ::Zip::DestinationFileExistsError - rescue ::Zip::ZipDestinationFileExistsError - end - - def test_rescue_legacy_zip_compression_method_error - raise ::Zip::CompressionMethodError - rescue ::Zip::ZipCompressionMethodError - end - - def test_rescue_legacy_zip_entry_name_error - raise ::Zip::EntryNameError - rescue ::Zip::ZipEntryNameError - end - - def test_rescue_legacy_zip_internal_error - raise ::Zip::InternalError - rescue ::Zip::ZipInternalError - end -end From a301d68eebd2f96afb2696e0ff79448176f282bc Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 26 Jun 2021 19:17:38 +0100 Subject: [PATCH 329/469] Raise an error if entry names exceed 65,535 characters. Fixes #247. --- .rubocop_todo.yml | 2 +- Changelog.md | 1 + lib/zip/entry.rb | 12 ++++++++++-- test/entry_test.rb | 9 +++++++++ 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 84dcc795..addf8759 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -20,7 +20,7 @@ Lint/MissingSuper: # Offense count: 5 # Configuration parameters: CountComments, CountAsOne. Metrics/ClassLength: - Max: 610 + Max: 620 # Offense count: 21 # Configuration parameters: IgnoredMethods. diff --git a/Changelog.md b/Changelog.md index 123a9f03..98bf6b5d 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,6 @@ # 3.0.0 (Next) +- Raise an error if entry names exceed 65,535 characters. [#247](https://github.com/rubyzip/rubyzip/issues/247) - Remove the `ZipXError` v1 legacy classes. - Raise an error on reading a split archive with `InputStream`. [#349](https://github.com/rubyzip/rubyzip/issues/349) - Ensure `InputStream` raises `GPFBit3Error` for OSX Archive files. [#493](https://github.com/rubyzip/rubyzip/issues/493) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 8b0c9bc4..749563d9 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -55,9 +55,17 @@ def set_default_vars_values end def check_name(name) - return unless name.start_with?('/') + error = + if name.start_with?('/') + "Illegal entry name '#{name}'. Names must not start with '/'" + elsif name.length > 65_535 + 'Illegal entry name. Names must have fewer than 65,536 characters' + else + '' + end + return if error.empty? - raise ::Zip::EntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /" + raise EntryNameError, error end def initialize( diff --git a/test/entry_test.rb b/test/entry_test.rb index d57962ca..17273c54 100644 --- a/test/entry_test.rb +++ b/test/entry_test.rb @@ -150,6 +150,15 @@ def test_entry_name_cannot_start_with_slash assert_raises(::Zip::EntryNameError) { ::Zip::Entry.new('zf.zip', '/hej/der') } end + def test_entry_name_cannot_be_too_long + name = 'a' * 65_535 + ::Zip::Entry.new('', name) # Should not raise anything. + + assert_raises(::Zip::EntryNameError) do + ::Zip::Entry.new('', "a#{name}") + end + end + def test_store_file_without_compression Dir.mktmpdir do |tmp| tmp_zip = File.join(tmp, 'no_compress.zip') From 8699e356d4e08550de9fc5873f8f4ddd63069014 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 26 Jun 2021 20:04:17 +0100 Subject: [PATCH 330/469] Improve documentation for `File.glob`. Closes #338. --- lib/zip/file.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 8b3a348e..a8d61642 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -330,7 +330,11 @@ def find_entry(entry_name) selected_entry end - # Searches for entries given a glob + # Search for entries given a glob pattern. You can also supply flags + # in the second argument, which are equivalent to those used by + # `::Dir.glob` and `::File.fnmatch`. Default flags are + # `::File::FNM_PATHNAME | ::File::FNM_DOTMATCH | ::File::FNM_EXTGLOB`, + # which will be overridden if you set your own flags. def glob(*args, &block) @entry_set.glob(*args, &block) end From f76cef90f7205210a7c4e6fc9d42e03e9c08f664 Mon Sep 17 00:00:00 2001 From: "Yuya.Nishida" Date: Sun, 27 Jun 2021 05:19:15 +0900 Subject: [PATCH 331/469] Remove newer duplicated line --- Changelog.md | 1 - 1 file changed, 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 98bf6b5d..011d560a 100644 --- a/Changelog.md +++ b/Changelog.md @@ -14,7 +14,6 @@ - Set the default `Entry` time to the file's mtime on Windows. [#465](https://github.com/rubyzip/rubyzip/issues/465) - Ensure that `Entry#time=` sets times as `DOSTime` objects. [#481](https://github.com/rubyzip/rubyzip/issues/481) - Replace and deprecate `Zip::DOSTime#dos_equals`. [#464](https://github.com/rubyzip/rubyzip/pull/464) -- Fix input stream partial read error. [#462](https://github.com/rubyzip/rubyzip/pull/462) - Fix loading extra fields. [#459](https://github.com/rubyzip/rubyzip/pull/459) - Set compression level on a per-zipfile basis. [#448](https://github.com/rubyzip/rubyzip/pull/448) - Fix input stream partial read error. [#462](https://github.com/rubyzip/rubyzip/pull/462) From e7f0aba5ffee55f017bbd6f0612465d4767e05ab Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 8 Jun 2021 17:13:20 +0100 Subject: [PATCH 332/469] Fix Style/OptionalBooleanParameter cop in `Entry`. Just an internal API so safe, and makes things a lot neater. --- .rubocop_todo.yml | 1 - lib/zip/entry.rb | 2 +- lib/zip/output_stream.rb | 2 +- test/local_entry_test.rb | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index addf8759..ae36271b 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -109,7 +109,6 @@ Style/NumericPredicate: # AllowedMethods: respond_to_missing? Style/OptionalBooleanParameter: Exclude: - - 'lib/zip/entry.rb' - 'lib/zip/file.rb' - 'lib/zip/file_split.rb' - 'lib/zip/output_stream.rb' diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 749563d9..11ddb851 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -333,7 +333,7 @@ def pack_local_entry @extra ? @extra.local_size : 0].pack('VvvvvvVVVvv') end - def write_local_entry(io, rewrite = false) #:nodoc:all + def write_local_entry(io, rewrite: false) #:nodoc:all prep_zip64_extra(true) verify_local_header_size! if rewrite @local_header_offset = io.tell diff --git a/lib/zip/output_stream.rb b/lib/zip/output_stream.rb index a648a3d0..2150dfb4 100644 --- a/lib/zip/output_stream.rb +++ b/lib/zip/output_stream.rb @@ -177,7 +177,7 @@ def update_local_headers pos = @output_stream.pos @entry_set.each do |entry| @output_stream.pos = entry.local_header_offset - entry.write_local_entry(@output_stream, true) + entry.write_local_entry(@output_stream, rewrite: true) end @output_stream.pos = pos end diff --git a/test/local_entry_test.rb b/test/local_entry_test.rb index 212c91af..15c367a7 100644 --- a/test/local_entry_test.rb +++ b/test/local_entry_test.rb @@ -126,7 +126,7 @@ def test_rewrite_local_header64 buf2 = StringIO.new entry.size = 0x123456789ABCDEF0 entry.compressed_size = 0x0123456789ABCDEF - entry.write_local_entry(buf2, true) + entry.write_local_entry(buf2, rewrite: true) refute_nil(entry.extra['Zip64']) refute_equal(buf1.size, 0) assert_equal(buf1.size, buf2.size) # it can't grow, or we'd clobber file data From 7ae90be63e8f2a52ad09fdbdcea6447dd3035a8f Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 8 Jun 2021 17:25:22 +0100 Subject: [PATCH 333/469] Fix Style/OptionalBooleanParameter in `OutputStream`. --- .rubocop_todo.yml | 1 - lib/zip/output_stream.rb | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index ae36271b..732f26d0 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -111,7 +111,6 @@ Style/OptionalBooleanParameter: Exclude: - 'lib/zip/file.rb' - 'lib/zip/file_split.rb' - - 'lib/zip/output_stream.rb' # Offense count: 17 # Cop supports --auto-correct. diff --git a/lib/zip/output_stream.rb b/lib/zip/output_stream.rb index 2150dfb4..ebf35b67 100644 --- a/lib/zip/output_stream.rb +++ b/lib/zip/output_stream.rb @@ -26,7 +26,7 @@ class OutputStream # Opens the indicated zip file. If a file with that name already # exists it will be overwritten. - def initialize(file_name, stream = false, encrypter = nil) + def initialize(file_name, stream: false, encrypter: nil) super() @file_name = file_name @output_stream = if stream @@ -52,7 +52,7 @@ class << self def open(file_name, encrypter = nil) return new(file_name) unless block_given? - zos = new(file_name, false, encrypter) + zos = new(file_name, stream: false, encrypter: encrypter) yield zos ensure zos.close if zos @@ -61,7 +61,7 @@ def open(file_name, encrypter = nil) # Same as #open but writes to a filestream instead def write_buffer(io = ::StringIO.new(''), encrypter = nil) io.binmode if io.respond_to?(:binmode) - zos = new(io, true, encrypter) + zos = new(io, stream: true, encrypter: encrypter) yield zos zos.close_buffer end From 659db85bffa7f4a5a18b9923d09c095062a19183 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 8 Jun 2021 17:32:29 +0100 Subject: [PATCH 334/469] `open` and `write_buffer` in `OutputStream` use named params. --- README.md | 4 +++- lib/zip/output_stream.rb | 4 ++-- test/encryption_test.rb | 5 ++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d70e2881..5af5454b 100644 --- a/README.md +++ b/README.md @@ -201,7 +201,9 @@ Any attempt to move about in a zip file opened with `Zip::InputStream` could res Rubyzip supports reading/writing zip files with traditional zip encryption (a.k.a. "ZipCrypto"). AES encryption is not yet supported. It can be used with buffer streams, e.g.: ```ruby -Zip::OutputStream.write_buffer(::StringIO.new(''), Zip::TraditionalEncrypter.new('password')) do |out| +Zip::OutputStream.write_buffer( + ::StringIO.new, encrypter: Zip::TraditionalEncrypter.new('password') +) do |out| out.put_next_entry("my_file.txt") out.write my_data end.string diff --git a/lib/zip/output_stream.rb b/lib/zip/output_stream.rb index ebf35b67..29408bdf 100644 --- a/lib/zip/output_stream.rb +++ b/lib/zip/output_stream.rb @@ -49,7 +49,7 @@ def initialize(file_name, stream: false, encrypter: nil) # stream is passed to the block and closed when the block # returns. class << self - def open(file_name, encrypter = nil) + def open(file_name, encrypter: nil) return new(file_name) unless block_given? zos = new(file_name, stream: false, encrypter: encrypter) @@ -59,7 +59,7 @@ def open(file_name, encrypter = nil) end # Same as #open but writes to a filestream instead - def write_buffer(io = ::StringIO.new(''), encrypter = nil) + def write_buffer(io = ::StringIO.new(''), encrypter: nil) io.binmode if io.respond_to?(:binmode) zos = new(io, stream: true, encrypter: encrypter) yield zos diff --git a/test/encryption_test.rb b/test/encryption_test.rb index c1bc2038..4289df12 100644 --- a/test/encryption_test.rb +++ b/test/encryption_test.rb @@ -20,7 +20,10 @@ def test_encrypt password = 'swordfish' - encrypted_zip = Zip::OutputStream.write_buffer(::StringIO.new(+''), Zip::TraditionalEncrypter.new(password)) do |out| + encrypted_zip = Zip::OutputStream.write_buffer( + ::StringIO.new(+''), + encrypter: Zip::TraditionalEncrypter.new(password) + ) do |out| out.put_next_entry(test_filename) out.write content end From e1e1cab39c3613055e87606a5e059636c3e6dd95 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 8 Jun 2021 18:09:22 +0100 Subject: [PATCH 335/469] Fix some non-writable `StringIO`s. --- lib/zip/file.rb | 4 ++-- lib/zip/output_stream.rb | 2 +- test/encryption_test.rb | 2 +- test/file_test.rb | 3 +-- test/output_stream_test.rb | 15 +++++++++++---- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index a8d61642..256cf656 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -128,7 +128,7 @@ def open(file_name, create = false, options = {}) # Same as #open. But outputs data to a buffer instead of a file def add_buffer - io = ::StringIO.new(+'') + io = ::StringIO.new zf = ::Zip::File.new(io, true, true) yield zf zf.write_buffer(io) @@ -297,7 +297,7 @@ def commit end # Write buffer write changes to buffer and return - def write_buffer(io = ::StringIO.new('')) + def write_buffer(io = ::StringIO.new) ::Zip::OutputStream.write_buffer(io) do |zos| @entry_set.each { |e| e.write_to_zip_output_stream(zos) } zos.comment = comment diff --git a/lib/zip/output_stream.rb b/lib/zip/output_stream.rb index 29408bdf..f2e7eb5e 100644 --- a/lib/zip/output_stream.rb +++ b/lib/zip/output_stream.rb @@ -59,7 +59,7 @@ def open(file_name, encrypter: nil) end # Same as #open but writes to a filestream instead - def write_buffer(io = ::StringIO.new(''), encrypter: nil) + def write_buffer(io = ::StringIO.new, encrypter: nil) io.binmode if io.respond_to?(:binmode) zos = new(io, stream: true, encrypter: encrypter) yield zos diff --git a/test/encryption_test.rb b/test/encryption_test.rb index 4289df12..150aea04 100644 --- a/test/encryption_test.rb +++ b/test/encryption_test.rb @@ -21,7 +21,7 @@ def test_encrypt password = 'swordfish' encrypted_zip = Zip::OutputStream.write_buffer( - ::StringIO.new(+''), + ::StringIO.new, encrypter: Zip::TraditionalEncrypter.new(password) ) do |out| out.put_next_entry(test_filename) diff --git a/test/file_test.rb b/test/file_test.rb index ec18aa2a..25b31b22 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -545,8 +545,7 @@ def test_write_buffer zf = ::Zip::File.new(TEST_ZIP.zip_name) old_name = zf.entries.first zf.rename(old_name, new_name) - io = ::StringIO.new(+'') - buffer = zf.write_buffer(io) + buffer = zf.write_buffer(::StringIO.new) File.open(TEST_ZIP.zip_name, 'wb') { |f| f.write buffer.string } zf_read = ::Zip::File.new(TEST_ZIP.zip_name) refute_nil(zf_read.entries.detect { |e| e.name == new_name }) diff --git a/test/output_stream_test.rb b/test/output_stream_test.rb index ecd48488..b87f64fc 100644 --- a/test/output_stream_test.rb +++ b/test/output_stream_test.rb @@ -25,8 +25,7 @@ def test_open end def test_write_buffer - io = ::StringIO.new(+'') - buffer = ::Zip::OutputStream.write_buffer(io) do |zos| + buffer = ::Zip::OutputStream.write_buffer(::StringIO.new) do |zos| zos.comment = TEST_ZIP.comment write_test_zip(zos) end @@ -35,8 +34,7 @@ def test_write_buffer end def test_write_buffer_binmode - io = ::StringIO.new(+'') - buffer = ::Zip::OutputStream.write_buffer(io) do |zos| + buffer = ::Zip::OutputStream.write_buffer(::StringIO.new) do |zos| zos.comment = TEST_ZIP.comment write_test_zip(zos) end @@ -72,6 +70,15 @@ def test_write_buffer_with_temp_file2 ::File.unlink(tmp_file) end + def test_write_buffer_with_default_io + buffer = ::Zip::OutputStream.write_buffer do |zos| + zos.comment = TEST_ZIP.comment + write_test_zip(zos) + end + File.open(TEST_ZIP.zip_name, 'wb') { |f| f.write buffer.string } + assert_test_zip_contents(TEST_ZIP) + end + def test_writing_to_closed_stream assert_i_o_error_in_closed_stream { |zos| zos << 'hello world' } assert_i_o_error_in_closed_stream { |zos| zos.puts 'hello world' } From f033ae760dd1d3ab9eef0da7b9c7dc2548f92dfd Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 8 Jun 2021 21:24:52 +0100 Subject: [PATCH 336/469] Use named parameters for `File::new`. This is a breaking change, but now is the time to do this as we've already done the same for `Entry::new`. --- .rubocop_todo.yml | 3 +- README.md | 6 ++-- lib/zip/file.rb | 21 ++++++------ lib/zip/filesystem.rb | 2 +- samples/example_filesystem.rb | 2 +- samples/example_recursive.rb | 2 +- test/case_sensitivity_test.rb | 6 ++-- test/entry_test.rb | 2 +- test/file_extract_test.rb | 2 +- test/file_options_test.rb | 20 +++++------ test/file_permissions_test.rb | 2 +- test/file_test.rb | 36 ++++++-------------- test/unicode_file_names_and_comments_test.rb | 2 +- test/zip64_full_test.rb | 2 +- 14 files changed, 45 insertions(+), 63 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 732f26d0..a08030fa 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -104,12 +104,11 @@ Style/NumericPredicate: - 'lib/zip/ioextras.rb' - 'lib/zip/ioextras/abstract_input_stream.rb' -# Offense count: 6 +# Offense count: 1 # Configuration parameters: AllowedMethods. # AllowedMethods: respond_to_missing? Style/OptionalBooleanParameter: Exclude: - - 'lib/zip/file.rb' - 'lib/zip/file_split.rb' # Offense count: 17 diff --git a/README.md b/README.md index 5af5454b..4b66ec20 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ input_filenames = ['image.jpg', 'description.txt', 'stats.csv'] zipfile_name = "/Users/me/Desktop/archive.zip" -Zip::File.open(zipfile_name, Zip::File::CREATE) do |zipfile| +Zip::File.open(zipfile_name, create: true) do |zipfile| input_filenames.each do |filename| # Two arguments: # - The name of the file as it will appear in the archive @@ -89,7 +89,7 @@ class ZipFileGenerator def write entries = Dir.entries(@input_dir) - %w[. ..] - ::Zip::File.open(@output_file, ::Zip::File::CREATE) do |zipfile| + ::Zip::File.open(@output_file, create: true) do |zipfile| write_entries entries, '', zipfile end end @@ -318,7 +318,7 @@ Where X is an integer between 0 and 9, inclusive. If this option is set to 0 (`Z This can also be set for each archive as an option to `Zip::File`: ```ruby -Zip::File.open('foo.zip', Zip::File::CREATE, {compression_level: 9}) do |zip| +Zip::File.open('foo.zip', create:true, compression_level: 9) do |zip| zip.add ... end ``` diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 256cf656..5623b4ea 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -25,7 +25,7 @@ module Zip # # require 'zip' # - # Zip::File.open("my.zip", Zip::File::CREATE) { + # Zip::File.open("my.zip", create: true) { # |zipfile| # zipfile.get_output_stream("first.txt") { |f| f.puts "Hello from ZipFile" } # zipfile.mkdir("a_dir") @@ -37,7 +37,7 @@ module Zip # # require 'zip' # - # Zip::File.open("my.zip", Zip::File::CREATE) { + # Zip::File.open("my.zip", create: true) { # |zipfile| # puts zipfile.read("first.txt") # zipfile.remove("first.txt") @@ -49,8 +49,7 @@ module Zip class File < CentralDirectory extend FileSplit - CREATE = true - IO_METHODS = [:tell, :seek, :read, :eof, :close].freeze + IO_METHODS = [:tell, :seek, :read, :eof, :close].freeze attr_reader :name @@ -66,9 +65,9 @@ class File < CentralDirectory # Returns the zip files comment, if it has one attr_accessor :comment - # Opens a zip archive. Pass true as the second parameter to create + # Opens a zip archive. Pass create: true to create # a new archive if it doesn't exist already. - def initialize(path_or_io, create = false, buffer = false, options = {}) + def initialize(path_or_io, create: false, buffer: false, **options) super() options = DEFAULT_RESTORE_OPTIONS .merge(compression_level: ::Zip.default_compression) @@ -115,8 +114,8 @@ class << self # Similar to ::new. If a block is passed the Zip::File object is passed # to the block and is automatically closed afterwards, just as with # ruby's builtin File::open method. - def open(file_name, create = false, options = {}) - zf = ::Zip::File.new(file_name, create, false, options) + def open(file_name, create: false, **options) + zf = ::Zip::File.new(file_name, create: create, **options) return zf unless block_given? begin @@ -129,7 +128,7 @@ def open(file_name, create = false, options = {}) # Same as #open. But outputs data to a buffer instead of a file def add_buffer io = ::StringIO.new - zf = ::Zip::File.new(io, true, true) + zf = ::Zip::File.new(io, create: true, buffer: true) yield zf zf.write_buffer(io) end @@ -138,7 +137,7 @@ def add_buffer # stream, and outputs data to a buffer. # (This can be used to extract data from a # downloaded zip archive without first saving it to disk.) - def open_buffer(io, options = {}) + def open_buffer(io, **options) unless IO_METHODS.map { |method| io.respond_to?(method) }.all? || io.kind_of?(String) raise 'Zip::File.open_buffer expects a String or IO-like argument' \ "(responds to #{IO_METHODS.join(', ')}). Found: #{io.class}" @@ -149,7 +148,7 @@ def open_buffer(io, options = {}) # https://github.com/rubyzip/rubyzip/issues/119 io.binmode if io.respond_to?(:binmode) - zf = ::Zip::File.new(io, true, true, options) + zf = ::Zip::File.new(io, create: true, buffer: true, **options) return zf unless block_given? yield zf diff --git a/lib/zip/filesystem.rb b/lib/zip/filesystem.rb index 26895df5..e223b3c0 100644 --- a/lib/zip/filesystem.rb +++ b/lib/zip/filesystem.rb @@ -21,7 +21,7 @@ module Zip # # require 'zip/filesystem' # - # Zip::File.open("my.zip", Zip::File::CREATE) { + # Zip::File.open("my.zip", create: true) { # |zipfile| # zipfile.file.open("first.txt", "w") { |f| f.puts "Hello world" } # zipfile.dir.mkdir("mydir") diff --git a/samples/example_filesystem.rb b/samples/example_filesystem.rb index d37eaee6..6909848f 100755 --- a/samples/example_filesystem.rb +++ b/samples/example_filesystem.rb @@ -9,7 +9,7 @@ File.delete(EXAMPLE_ZIP) if File.exist?(EXAMPLE_ZIP) -Zip::File.open(EXAMPLE_ZIP, Zip::File::CREATE) do |zf| +Zip::File.open(EXAMPLE_ZIP, create: true) do |zf| zf.file.open('file1.txt', 'w') { |os| os.write 'first file1.txt' } zf.dir.mkdir('dir1') zf.dir.chdir('dir1') diff --git a/samples/example_recursive.rb b/samples/example_recursive.rb index 175e69f4..04b6f339 100644 --- a/samples/example_recursive.rb +++ b/samples/example_recursive.rb @@ -23,7 +23,7 @@ def initialize(input_dir, output_file) def write entries = Dir.entries(@input_dir) - %w[. ..] - ::Zip::File.open(@output_file, ::Zip::File::CREATE) do |zipfile| + ::Zip::File.open(@output_file, create: true) do |zipfile| write_entries entries, '', zipfile end end diff --git a/test/case_sensitivity_test.rb b/test/case_sensitivity_test.rb index af4443db..81ad2072 100644 --- a/test/case_sensitivity_test.rb +++ b/test/case_sensitivity_test.rb @@ -19,7 +19,7 @@ def test_add_case_sensitive ::Zip.case_insensitive_match = false SRC_FILES.each { |fn, _en| assert(::File.exist?(fn)) } - zf = ::Zip::File.new(EMPTY_FILENAME, ::Zip::File::CREATE) + zf = ::Zip::File.new(EMPTY_FILENAME, create: true) SRC_FILES.each { |fn, en| zf.add(en, fn) } zf.close @@ -38,7 +38,7 @@ def test_add_case_insensitive ::Zip.case_insensitive_match = true SRC_FILES.each { |fn, _en| assert(::File.exist?(fn)) } - zf = ::Zip::File.new(EMPTY_FILENAME, ::Zip::File::CREATE) + zf = ::Zip::File.new(EMPTY_FILENAME, create: true) assert_raises Zip::EntryExistsError do SRC_FILES.each { |fn, en| zf.add(en, fn) } @@ -50,7 +50,7 @@ def test_add_case_sensitive_read_case_insensitive ::Zip.case_insensitive_match = false SRC_FILES.each { |fn, _en| assert(::File.exist?(fn)) } - zf = ::Zip::File.new(EMPTY_FILENAME, ::Zip::File::CREATE) + zf = ::Zip::File.new(EMPTY_FILENAME, create: true) SRC_FILES.each { |fn, en| zf.add(en, fn) } zf.close diff --git a/test/entry_test.rb b/test/entry_test.rb index 17273c54..13d42963 100644 --- a/test/entry_test.rb +++ b/test/entry_test.rb @@ -167,7 +167,7 @@ def test_store_file_without_compression z.write_zip64_support = false end - zipfile = Zip::File.open(tmp_zip, Zip::File::CREATE) + zipfile = Zip::File.open(tmp_zip, create: true) mimetype_entry = Zip::Entry.new( zipfile, # @zipfile diff --git a/test/file_extract_test.rb b/test/file_extract_test.rb index 9d0b7da4..e5243cab 100644 --- a/test/file_extract_test.rb +++ b/test/file_extract_test.rb @@ -99,7 +99,7 @@ def test_extract_incorrect_size true_size = 500_000 fake_size = 1 - ::Zip::File.open(real_zip, ::Zip::File::CREATE) do |zf| + ::Zip::File.open(real_zip, create: true) do |zf| zf.get_output_stream(file_name) do |os| os.write 'a' * true_size end diff --git a/test/file_options_test.rb b/test/file_options_test.rb index da9a402c..c1a568a6 100644 --- a/test/file_options_test.rb +++ b/test/file_options_test.rb @@ -30,13 +30,13 @@ def test_restore_permissions_true ::FileUtils.cp(TXTPATH, TXTPATH_755) ::File.chmod(0o755, TXTPATH_755) - ::Zip::File.open(ZIPPATH, true) do |zip| + ::Zip::File.open(ZIPPATH, create: true) do |zip| zip.add(ENTRY_1, TXTPATH) zip.add(ENTRY_2, TXTPATH_600) zip.add(ENTRY_3, TXTPATH_755) end - ::Zip::File.open(ZIPPATH, false, restore_permissions: true) do |zip| + ::Zip::File.open(ZIPPATH, restore_permissions: true) do |zip| zip.extract(ENTRY_1, EXTPATH_1) zip.extract(ENTRY_2, EXTPATH_2) zip.extract(ENTRY_3, EXTPATH_3) @@ -54,13 +54,13 @@ def test_restore_permissions_false ::FileUtils.cp(TXTPATH, TXTPATH_755) ::File.chmod(0o755, TXTPATH_755) - ::Zip::File.open(ZIPPATH, true) do |zip| + ::Zip::File.open(ZIPPATH, create: true) do |zip| zip.add(ENTRY_1, TXTPATH) zip.add(ENTRY_2, TXTPATH_600) zip.add(ENTRY_3, TXTPATH_755) end - ::Zip::File.open(ZIPPATH, false, restore_permissions: false) do |zip| + ::Zip::File.open(ZIPPATH, restore_permissions: false) do |zip| zip.extract(ENTRY_1, EXTPATH_1) zip.extract(ENTRY_2, EXTPATH_2) zip.extract(ENTRY_3, EXTPATH_3) @@ -79,7 +79,7 @@ def test_restore_permissions_as_default ::FileUtils.cp(TXTPATH, TXTPATH_755) ::File.chmod(0o755, TXTPATH_755) - ::Zip::File.open(ZIPPATH, true) do |zip| + ::Zip::File.open(ZIPPATH, create: true) do |zip| zip.add(ENTRY_1, TXTPATH) zip.add(ENTRY_2, TXTPATH_600) zip.add(ENTRY_3, TXTPATH_755) @@ -97,12 +97,12 @@ def test_restore_permissions_as_default end def test_restore_times_true - ::Zip::File.open(ZIPPATH, true) do |zip| + ::Zip::File.open(ZIPPATH, create: true) do |zip| zip.add(ENTRY_1, TXTPATH) zip.add_stored(ENTRY_2, TXTPATH) end - ::Zip::File.open(ZIPPATH, false, restore_times: true) do |zip| + ::Zip::File.open(ZIPPATH, restore_times: true) do |zip| zip.extract(ENTRY_1, EXTPATH_1) zip.extract(ENTRY_2, EXTPATH_2) end @@ -112,12 +112,12 @@ def test_restore_times_true end def test_restore_times_false - ::Zip::File.open(ZIPPATH, true) do |zip| + ::Zip::File.open(ZIPPATH, create: true) do |zip| zip.add(ENTRY_1, TXTPATH) zip.add_stored(ENTRY_2, TXTPATH) end - ::Zip::File.open(ZIPPATH, false, restore_times: false) do |zip| + ::Zip::File.open(ZIPPATH, restore_times: false) do |zip| zip.extract(ENTRY_1, EXTPATH_1) zip.extract(ENTRY_2, EXTPATH_2) end @@ -127,7 +127,7 @@ def test_restore_times_false end def test_restore_times_true_as_default - ::Zip::File.open(ZIPPATH, true) do |zip| + ::Zip::File.open(ZIPPATH, create: true) do |zip| zip.add(ENTRY_1, TXTPATH) zip.add_stored(ENTRY_2, TXTPATH) end diff --git a/test/file_permissions_test.rb b/test/file_permissions_test.rb index 1b31a79b..e9dabf3f 100644 --- a/test/file_permissions_test.rb +++ b/test/file_permissions_test.rb @@ -48,7 +48,7 @@ def assert_matching_permissions(expected_file, actual_file) end def create_files - ::Zip::File.open(ZIPNAME, ::Zip::File::CREATE) do |zip| + ::Zip::File.open(ZIPNAME, create: true) do |zip| zip.comment = 'test' end diff --git a/test/file_test.rb b/test/file_test.rb index 25b31b22..9ac2f698 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -32,21 +32,7 @@ def test_create_from_scratch_to_buffer def test_create_from_scratch comment = 'a short comment' - zf = ::Zip::File.new(EMPTY_FILENAME, ::Zip::File::CREATE) - zf.get_output_stream('myFile') { |os| os.write 'myFile contains just this' } - zf.mkdir('dir1') - zf.comment = comment - zf.close - - zf_read = ::Zip::File.new(EMPTY_FILENAME) - assert_equal(comment, zf_read.comment) - assert_equal(2, zf_read.entries.length) - end - - def test_create_from_scratch_with_old_create_parameter - comment = 'a short comment' - - zf = ::Zip::File.new(EMPTY_FILENAME, 1) + zf = ::Zip::File.new(EMPTY_FILENAME, create: true) zf.get_output_stream('myFile') { |os| os.write 'myFile contains just this' } zf.mkdir('dir1') zf.comment = comment @@ -184,7 +170,7 @@ def test_open_buffer_without_block end def test_cleans_up_tempfiles_after_close - zf = ::Zip::File.new(EMPTY_FILENAME, ::Zip::File::CREATE) + zf = ::Zip::File.new(EMPTY_FILENAME, create: true) zf.get_output_stream('myFile') do |os| @tempfile_path = os.path os.write 'myFile contains just this' @@ -208,9 +194,7 @@ def test_add_different_compression sizes = [] files.each do |name, comp| - zf = ::Zip::File.new( - name, ::Zip::File::CREATE, false, { compression_level: comp } - ) + zf = ::Zip::File.new(name, create: true, compression_level: comp) zf.add(entry_name, src_file) zf.close @@ -243,7 +227,7 @@ def test_add_different_compression_as_default files.each do |name, comp| ::Zip.default_compression = comp - zf = ::Zip::File.new(name, ::Zip::File::CREATE) + zf = ::Zip::File.new(name, create: true) zf.add(entry_name, src_file) zf.close @@ -268,7 +252,7 @@ def test_add_stored src_file = 'test/data/file2.txt' entry_name = 'newEntryName.rb' assert(::File.exist?(src_file)) - zf = ::Zip::File.new(EMPTY_FILENAME, ::Zip::File::CREATE) + zf = ::Zip::File.new(EMPTY_FILENAME, create: true) zf.add_stored(entry_name, src_file) zf.close @@ -294,7 +278,7 @@ def test_recover_permissions_after_add_files_to_archive ::File.chmod(0o664, src_zip) assert_equal(0o100664, ::File.stat(src_zip).mode) - zf = ::Zip::File.new(src_zip, ::Zip::File::CREATE) + zf = ::Zip::File.new(src_zip, create: true) zf.add('newEntryName.rb', 'test/data/file2.txt') zf.close @@ -385,7 +369,7 @@ def test_rename_with_each ::File.unlink(zf_name) if ::File.exist?(zf_name) arr = [] arr_renamed = [] - ::Zip::File.open(zf_name, ::Zip::File::CREATE) do |zf| + ::Zip::File.open(zf_name, create: true) do |zf| zf.mkdir('test') arr << 'test/' arr_renamed << 'Ztest/' @@ -398,7 +382,7 @@ def test_rename_with_each zf = ::Zip::File.open(zf_name) assert_equal(zf.entries.map(&:name), arr) zf.close - Zip::File.open(zf_name, 'wb') do |z| + Zip::File.open(zf_name) do |z| z.each do |f| z.rename(f, "Z#{f.name}") end @@ -522,7 +506,7 @@ def test_commit def test_double_commit(filename = 'test/data/generated/double_commit_test.zip') ::FileUtils.touch('test/data/generated/test_double_commit1.txt') ::FileUtils.touch('test/data/generated/test_double_commit2.txt') - zf = ::Zip::File.open(filename, ::Zip::File::CREATE) + zf = ::Zip::File.open(filename, create: true) zf.add('test1.txt', 'test/data/generated/test_double_commit1.txt') zf.commit zf.add('test2.txt', 'test/data/generated/test_double_commit2.txt') @@ -695,7 +679,7 @@ def test_preserve_file_order def test_streaming fname = ::File.join(__dir__, '..', 'README.md') zname = 'test/data/generated/README.zip' - Zip::File.open(zname, Zip::File::CREATE) do |zipfile| + Zip::File.open(zname, create: true) do |zipfile| zipfile.get_output_stream(File.basename(fname)) do |f| f.puts File.read(fname) end diff --git a/test/unicode_file_names_and_comments_test.rb b/test/unicode_file_names_and_comments_test.rb index ceac94fa..bc843321 100644 --- a/test/unicode_file_names_and_comments_test.rb +++ b/test/unicode_file_names_and_comments_test.rb @@ -53,7 +53,7 @@ def test_unicode_file_name def test_unicode_comment str = '渠道升级' - ::Zip::File.open(FILENAME, Zip::File::CREATE) do |z| + ::Zip::File.open(FILENAME, create: true) do |z| z.comment = str end diff --git a/test/zip64_full_test.rb b/test/zip64_full_test.rb index 5d174e3b..0837f733 100644 --- a/test/zip64_full_test.rb +++ b/test/zip64_full_test.rb @@ -21,7 +21,7 @@ def test_large_zip_file last_text = 'this tests files starting after 4GB in the archive' comment_text = 'this is a file comment in a zip64 archive' - ::Zip::File.open(HUGE_ZIP, ::Zip::File::CREATE) do |zf| + ::Zip::File.open(HUGE_ZIP, create: true) do |zf| zf.comment = comment_text zf.get_output_stream('first_file.txt') do |io| From debc9fda91e767cdaa40dea9321dab13211c5df1 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 13 Jun 2021 10:18:15 +0100 Subject: [PATCH 337/469] Use named parameters for `File::split`. --- .rubocop_todo.yml | 7 ------- lib/zip/file_split.rb | 6 +++--- test/file_split_test.rb | 4 +++- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index a08030fa..1afa96b1 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -104,13 +104,6 @@ Style/NumericPredicate: - 'lib/zip/ioextras.rb' - 'lib/zip/ioextras/abstract_input_stream.rb' -# Offense count: 1 -# Configuration parameters: AllowedMethods. -# AllowedMethods: respond_to_missing? -Style/OptionalBooleanParameter: - Exclude: - - 'lib/zip/file_split.rb' - # Offense count: 17 # Cop supports --auto-correct. # Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods. diff --git a/lib/zip/file_split.rb b/lib/zip/file_split.rb index 1b6a456c..606d2e6b 100644 --- a/lib/zip/file_split.rb +++ b/lib/zip/file_split.rb @@ -67,8 +67,8 @@ def save_splited_part( # Splits an archive into parts with segment size def split( - zip_file_name, segment_size = MAX_SEGMENT_SIZE, - delete_zip_file = true, partial_zip_file_name = nil + zip_file_name, segment_size: MAX_SEGMENT_SIZE, + delete_original: true, partial_zip_file_name: nil ) raise Error, "File #{zip_file_name} not found" unless ::File.exist?(zip_file_name) raise Errno::ENOENT, zip_file_name unless ::File.readable?(zip_file_name) @@ -90,7 +90,7 @@ def split( ) end end - ::File.delete(zip_file_name) if delete_zip_file + ::File.delete(zip_file_name) if delete_original szip_file_index end end diff --git a/test/file_split_test.rb b/test/file_split_test.rb index 517723dc..0e54cc99 100644 --- a/test/file_split_test.rb +++ b/test/file_split_test.rb @@ -27,7 +27,9 @@ def test_split_method_respond end def test_split - result = ::Zip::File.split(TEST_ZIP.zip_name, 65_536, false) + result = ::Zip::File.split( + TEST_ZIP.zip_name, segment_size: 65_536, delete_original: false + ) return if result.nil? From f75eb615789490df85e2b35cb2c7fc9b07718cd9 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 14 Jun 2021 07:43:34 +0100 Subject: [PATCH 338/469] Use named parameters for `File#get_output_stream`. --- lib/zip/file.rb | 10 +++---- lib/zip/filesystem/zip_file_name_mapper.rb | 2 +- test/file_test.rb | 32 ++++++++++++++-------- test/zip64_full_test.rb | 2 +- 4 files changed, 27 insertions(+), 19 deletions(-) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 5623b4ea..576639b5 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -185,10 +185,10 @@ def get_input_stream(entry, &a_proc) # specified. If a block is passed the stream object is passed to the block and # the stream is automatically closed afterwards just as with ruby's builtin # File.open method. - 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) new_entry = if entry.kind_of?(Entry) @@ -205,7 +205,7 @@ def get_output_stream(entry, permission_int = nil, comment = nil, raise ArgumentError, "cannot open stream to directory entry - '#{new_entry}'" end - new_entry.unix_perms = permission_int + new_entry.unix_perms = permissions zip_streamable_entry = StreamableStream.new(new_entry) @entry_set << zip_streamable_entry zip_streamable_entry.get_output_stream(&a_proc) diff --git a/lib/zip/filesystem/zip_file_name_mapper.rb b/lib/zip/filesystem/zip_file_name_mapper.rb index aa699011..6b9c7f51 100644 --- a/lib/zip/filesystem/zip_file_name_mapper.rb +++ b/lib/zip/filesystem/zip_file_name_mapper.rb @@ -28,7 +28,7 @@ def get_input_stream(filename, &a_proc) def get_output_stream(filename, permissions = nil, &a_proc) @zip_file.get_output_stream( - expand_to_entry(filename), permissions, &a_proc + expand_to_entry(filename), permissions: permissions, &a_proc ) end diff --git a/test/file_test.rb b/test/file_test.rb index 9ac2f698..c183c26d 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -68,22 +68,30 @@ def test_get_output_stream assert_equal(count + 1, zf.size) assert_equal('Putting stuff in data/generated/empty.txt', zf.read('test/data/generated/empty.txt')) - custom_entry_args = [ - TEST_COMMENT, TEST_EXTRA, TEST_COMPRESSED_SIZE, TEST_CRC, - ::Zip::Entry::STORED, ::Zlib::BEST_SPEED, TEST_SIZE, TEST_TIME - ] - zf.get_output_stream('entry_with_custom_args.txt', nil, *custom_entry_args) do |os| + custom_entry_args = { + comment: TEST_COMMENT, compressed_size: TEST_COMPRESSED_SIZE, + crc: TEST_CRC, compression_method: ::Zip::COMPRESSION_METHOD_STORE, + compression_level: ::Zlib::BEST_SPEED, size: TEST_SIZE, time: TEST_TIME + } + zf.get_output_stream( + 'entry_with_custom_args.txt', **custom_entry_args + ) do |os| os.write 'Some data' end + assert_equal(count + 2, zf.size) entry = zf.get_entry('entry_with_custom_args.txt') - assert_equal(custom_entry_args[0], entry.comment) - assert_equal(custom_entry_args[2], entry.compressed_size) - assert_equal(custom_entry_args[3], entry.crc) - assert_equal(custom_entry_args[4], entry.compression_method) - assert_equal(custom_entry_args[5], entry.compression_level) - assert_equal(custom_entry_args[6], entry.size) - assert_equal(custom_entry_args[7], entry.time) + assert_equal(custom_entry_args[:comment], entry.comment) + assert_equal(custom_entry_args[:compressed_size], entry.compressed_size) + assert_equal(custom_entry_args[:crc], entry.crc) + assert_equal( + custom_entry_args[:compression_method], entry.compression_method + ) + assert_equal( + custom_entry_args[:compression_level], entry.compression_level + ) + assert_equal(custom_entry_args[:size], entry.size) + assert_equal(custom_entry_args[:time], entry.time) zf.get_output_stream('entry.bin') do |os| os.write(::File.open('test/data/generated/5entry.zip', 'rb').read) diff --git a/test/zip64_full_test.rb b/test/zip64_full_test.rb index 0837f733..7a4e0911 100644 --- a/test/zip64_full_test.rb +++ b/test/zip64_full_test.rb @@ -31,7 +31,7 @@ def test_large_zip_file # Write just over 4GB (stored, so the zip file exceeds 4GB). buf = 'blah' * 16_384 zf.get_output_stream( - 'huge_file', nil, nil, nil, nil, nil, ::Zip::Entry::STORED + 'huge_file', compression_method: ::Zip::COMPRESSION_METHOD_STORE ) do |io| 65_537.times { io.write(buf) } end From aa646ef827f2e819c075a31f83659ac6b09be6b1 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Fri, 18 Jun 2021 14:34:41 +0100 Subject: [PATCH 339/469] Use named params for `InputStream`. --- lib/zip/entry.rb | 2 +- lib/zip/input_stream.rb | 10 +++++----- test/encryption_test.rb | 14 +++++++++++--- test/stored_support_test.rb | 4 +++- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 11ddb851..83ced75e 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -577,7 +577,7 @@ def get_input_stream(&block) raise "unknown @file_type #{@ftype}" end else - zis = ::Zip::InputStream.new(@zipfile, local_header_offset) + zis = ::Zip::InputStream.new(@zipfile, offset: local_header_offset) zis.instance_variable_set(:@complete_entry, self) zis.get_next_entry if block diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index 1eb4080f..00b1d99b 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -51,7 +51,7 @@ class InputStream # # @param context [String||IO||StringIO] file path or IO/StringIO object # @param offset [Integer] offset in the IO/StringIO - def initialize(context, offset = 0, decrypter = nil) + def initialize(context, offset: 0, decrypter: nil) super() @archive_io = get_io(context, offset) @decompressor = ::Zip::NullDecompressor @@ -99,8 +99,8 @@ class << self # Same as #initialize but if a block is passed the opened # stream is passed to the block and closed when the block # returns. - def open(filename_or_io, offset = 0, decrypter = nil) - zio = new(filename_or_io, offset, decrypter) + def open(filename_or_io, offset: 0, decrypter: nil) + zio = new(filename_or_io, offset: offset, decrypter: decrypter) return zio unless block_given? begin @@ -110,9 +110,9 @@ def open(filename_or_io, offset = 0, decrypter = nil) end end - def open_buffer(filename_or_io, offset = 0) + def open_buffer(filename_or_io, offset: 0) warn 'open_buffer is deprecated!!! Use open instead!' - ::Zip::InputStream.open(filename_or_io, offset) + ::Zip::InputStream.open(filename_or_io, offset: offset) end end diff --git a/test/encryption_test.rb b/test/encryption_test.rb index 150aea04..74fe2ecf 100644 --- a/test/encryption_test.rb +++ b/test/encryption_test.rb @@ -28,7 +28,9 @@ def test_encrypt out.write content end - Zip::InputStream.open(encrypted_zip, 0, Zip::TraditionalDecrypter.new(password)) do |zis| + Zip::InputStream.open( + encrypted_zip, decrypter: Zip::TraditionalDecrypter.new(password) + ) do |zis| entry = zis.get_next_entry assert_equal test_filename, entry.name assert_equal 1327, entry.size @@ -36,7 +38,10 @@ def test_encrypt end assert_raises(Zip::DecompressionError) do - Zip::InputStream.open(encrypted_zip, 0, Zip::TraditionalDecrypter.new("#{password}wrong")) do |zis| + Zip::InputStream.open( + encrypted_zip, + decrypter: Zip::TraditionalDecrypter.new("#{password}wrong") + ) do |zis| zis.get_next_entry assert_equal content, zis.read end @@ -44,7 +49,10 @@ def test_encrypt end def test_decrypt - Zip::InputStream.open(ENCRYPT_ZIP_TEST_FILE, 0, Zip::TraditionalDecrypter.new('password')) do |zis| + Zip::InputStream.open( + ENCRYPT_ZIP_TEST_FILE, + decrypter: Zip::TraditionalDecrypter.new('password') + ) do |zis| entry = zis.get_next_entry assert_equal 'file1.txt', entry.name assert_equal 1327, entry.size diff --git a/test/stored_support_test.rb b/test/stored_support_test.rb index 1a125916..16f1bed2 100644 --- a/test/stored_support_test.rb +++ b/test/stored_support_test.rb @@ -22,7 +22,9 @@ def test_read end def test_encrypted_read - Zip::InputStream.open(ENCRYPTED_STORED_ZIP_TEST_FILE, 0, Zip::TraditionalDecrypter.new('password')) do |zis| + Zip::InputStream.open( + ENCRYPTED_STORED_ZIP_TEST_FILE, decrypter: Zip::TraditionalDecrypter.new('password') + ) do |zis| entry = zis.get_next_entry assert_equal 'file1.txt', entry.name assert_equal 1_327, entry.size From 9fc12bf97b029c9219d6390651f6312c592c9dda Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 20 Jun 2021 10:28:48 +0100 Subject: [PATCH 340/469] Add notes to the README and Changelog about the new API. --- Changelog.md | 1 + README.md | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 011d560a..ecabf38c 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,6 @@ # 3.0.0 (Next) +- Use named parameters for optional arguments in the public API. - Raise an error if entry names exceed 65,535 characters. [#247](https://github.com/rubyzip/rubyzip/issues/247) - Remove the `ZipXError` v1 legacy classes. - Raise an error on reading a split archive with `InputStream`. [#349](https://github.com/rubyzip/rubyzip/issues/349) diff --git a/README.md b/README.md index 4b66ec20..9fa7d5bb 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,17 @@ Rubyzip is a ruby library for reading and writing zip files. -## Important note +## Important notes + +### Version 3.0 + +The public API of some classes has been modernized to use named parameters for optional arguments. Please check your usage of the following Rubyzip classes: +* `File` +* `Entry` +* `InputStream` +* `OutputStream` + +### Older versions (pre 2.0) The Rubyzip interface has changed!!! No need to do `require "zip/zip"` and `Zip` prefix in class names removed. From 1fb74bd82f35b84c0fd4b7fbabbc2b97695fc973 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 27 Jun 2021 11:04:53 +0100 Subject: [PATCH 341/469] Don't mess with the library path in the gemspec. --- rubyzip.gemspec | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/rubyzip.gemspec b/rubyzip.gemspec index 295601a0..c0793e4a 100644 --- a/rubyzip.gemspec +++ b/rubyzip.gemspec @@ -1,8 +1,6 @@ # frozen_string_literal: true -lib = File.expand_path('lib', __dir__) -$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) -require 'zip/version' +require_relative 'lib/zip/version' Gem::Specification.new do |s| s.name = 'rubyzip' From 81d95ad0a3b5973beaccab9f443537a5a814266d Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 27 Jun 2021 11:18:24 +0100 Subject: [PATCH 342/469] Minor gemspec formatting changes for space/readability. --- .rubocop.yml | 3 ++- rubyzip.gemspec | 25 ++++++++++++++----------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index d3db8664..c6d2a51d 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -48,9 +48,10 @@ Metrics/AbcSize: Exclude: - 'test/**/*.rb' -# Turn block length metrics off for the tests. +# Turn block length metrics off for the tests and gemspec. Metrics/BlockLength: Exclude: + - 'rubyzip.gemspec' - 'test/**/*.rb' # Turn class length metrics off for the tests. diff --git a/rubyzip.gemspec b/rubyzip.gemspec index c0793e4a..5f2da555 100644 --- a/rubyzip.gemspec +++ b/rubyzip.gemspec @@ -3,24 +3,27 @@ require_relative 'lib/zip/version' Gem::Specification.new do |s| - s.name = 'rubyzip' - s.version = ::Zip::VERSION - s.authors = ['Alexander Simonov'] - s.email = ['alex@simonov.me'] - s.homepage = 'http://github.com/rubyzip/rubyzip' - s.platform = Gem::Platform::RUBY - s.summary = 'rubyzip is a ruby module for reading and writing zip files' - s.files = Dir.glob('{samples,lib}/**/*.rb') + %w[README.md TODO Rakefile] - s.require_paths = ['lib'] - s.license = 'BSD-2-Clause' - s.metadata = { + s.name = 'rubyzip' + s.version = ::Zip::VERSION + s.authors = ['Alexander Simonov'] + s.email = ['alex@simonov.me'] + s.homepage = 'http://github.com/rubyzip/rubyzip' + s.platform = Gem::Platform::RUBY + s.summary = 'rubyzip is a ruby module for reading and writing zip files' + s.files = Dir.glob('{samples,lib}/**/*.rb') + %w[README.md TODO Rakefile] + s.require_paths = ['lib'] + s.license = 'BSD-2-Clause' + + s.metadata = { 'bug_tracker_uri' => 'https://github.com/rubyzip/rubyzip/issues', 'changelog_uri' => "https://github.com/rubyzip/rubyzip/blob/v#{s.version}/Changelog.md", 'documentation_uri' => "https://www.rubydoc.info/gems/rubyzip/#{s.version}", 'source_code_uri' => "https://github.com/rubyzip/rubyzip/tree/v#{s.version}", 'wiki_uri' => 'https://github.com/rubyzip/rubyzip/wiki' } + s.required_ruby_version = '>= 2.4' + s.add_development_dependency 'minitest', '~> 5.4' s.add_development_dependency 'rake', '~> 12.3.3' s.add_development_dependency 'rubocop', '~> 1.12.0' From f005ca28645d838a01dd6993db4b04d93b60fd90 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 27 Jun 2021 11:30:09 +0100 Subject: [PATCH 343/469] Update the list of files packaged in the gemspec. --- rubyzip.gemspec | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rubyzip.gemspec b/rubyzip.gemspec index 5f2da555..15a50350 100644 --- a/rubyzip.gemspec +++ b/rubyzip.gemspec @@ -10,7 +10,8 @@ Gem::Specification.new do |s| s.homepage = 'http://github.com/rubyzip/rubyzip' s.platform = Gem::Platform::RUBY s.summary = 'rubyzip is a ruby module for reading and writing zip files' - s.files = Dir.glob('{samples,lib}/**/*.rb') + %w[README.md TODO Rakefile] + s.files = Dir.glob('{samples,lib}/**/*.rb') + + %w[README.md Changelog.md Rakefile rubyzip.gemspec] s.require_paths = ['lib'] s.license = 'BSD-2-Clause' From bb237aaa085d1b734d2e9e0720fb077a7b7be7f5 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 27 Jun 2021 11:47:31 +0100 Subject: [PATCH 344/469] Update authors in gemspec to reflect current maintainers. --- rubyzip.gemspec | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rubyzip.gemspec b/rubyzip.gemspec index 15a50350..1a913c03 100644 --- a/rubyzip.gemspec +++ b/rubyzip.gemspec @@ -5,8 +5,10 @@ require_relative 'lib/zip/version' Gem::Specification.new do |s| s.name = 'rubyzip' s.version = ::Zip::VERSION - s.authors = ['Alexander Simonov'] - s.email = ['alex@simonov.me'] + s.authors = ['Robert Haines', 'John Lees-Miller', 'Alexander Simonov'] + s.email = [ + 'hainesr@gmail.com', 'jdleesmiller@gmail.com', 'alex@simonov.me' + ] s.homepage = 'http://github.com/rubyzip/rubyzip' s.platform = Gem::Platform::RUBY s.summary = 'rubyzip is a ruby module for reading and writing zip files' From 50dddca0be55fc40c84470a2d7cba9ab81e33f6b Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 27 Jun 2021 15:30:06 +0100 Subject: [PATCH 345/469] Update encrypted fixtures to remove data descriptors. --- test/data/zipWithEncryption.zip | Bin 612 -> 9596 bytes .../zipWithStoredCompressionAndEncryption.zip | Bin 42931 -> 42899 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/test/data/zipWithEncryption.zip b/test/data/zipWithEncryption.zip index e102b875b651a4cf13aa623003b155f50ede1084..08215fcb5ad86ee4d74b2bdef5d3cea557dad25f 100644 GIT binary patch literal 9596 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 g-{r`_{zL9R8JPb|4if5LcftOtoPYWV`Mz(Tt z_HRm8cs;%5R$jJt@O}-GAi;ot&v&*yiCF!%JH zH%)uhQ}^BomPnV9_@g_AXG6@KNghJZ`G>g_H`$$Rn6%G&`NjsZLuxvu4}X3+zNL1e z^W+W1(~2)vD^2w)sh_Bs|LE|c*-;Anrfn#iWhA!Cr)7$Cwx9DK;i&%;{1?nWch_An z@X7At;H6Hvucl-iDlM4sdB*I;Q>q0geArXTvwZrc?ugQ#a_?}f9=rKle69MT2a{UZP$Kpco9*sq{@4B$@7KldNyr0{Li>< zL;U(U+3xD(C8dG?FV^-Yb@+yB7?<7st@UE%e7%mJ{<=p4-^j3LEt&8{@YVD<^ZYgW z9d1q@55I}}eQwm|7vK?RGTzF--*v>2XOm6!=SN-sA)R}R*H~&v3MN{V_E=qD=M&%0;!y5{SJu$5ktm*&<4c(ZdLM}B}eBa;a;?t}qM8w|j7!my+f X#DXW20B=?{kN_hPngeNZCJ+w*rLGJO diff --git a/test/data/zipWithStoredCompressionAndEncryption.zip b/test/data/zipWithStoredCompressionAndEncryption.zip index 2fd545e977663a1caddcfd851accb3c6baeb5252..f2d9c1632ce16f094523e202e1120c77b8ef5a23 100644 GIT binary patch literal 42899 zcmV)BK*PUKO9KQ7009610G*h8PrU7cr#l4z051gq00{sb0A^`yWic*vcyv`%2>=61 z4B%amqWoQTcnbgl1n2_*00ig*0065t1T9z^6mU>-kx^>r-dSuBQj@)*o%=Ye@tNw7 z7JjS;tt=c6m>m)1ykdqO$6I97jFNod);?&kc@iqYlXUgC$bRK5@=yPm{kEWbv+1EK z904HYt^4*c!{$Fi!3}<^x(VyCEs=?YIs;$m_p&6LXdOk0;SQDCjp$R4OaIWapq4&* zwS&PV9Yd3sQiK0gk38cnA=8$owiyY~+`E3Jj|BK`< zwgwoLn1D3eoxCPMX?Ae7&lBg6=2%0Ao(Z7F!p5!*$Czh&?@{dse<9EL)Md5pRPW^X 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 literal 42931 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�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 z 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 From 8071290ce67967dc40ffc4fc6ea965f78710fc68 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 27 Jun 2021 15:56:39 +0100 Subject: [PATCH 346/469] Update and tidy up encryption tests. --- test/encryption_test.rb | 9 +++++++-- test/stored_support_test.rb | 3 ++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/test/encryption_test.rb b/test/encryption_test.rb index 74fe2ecf..bed3fe68 100644 --- a/test/encryption_test.rb +++ b/test/encryption_test.rb @@ -5,6 +5,7 @@ class EncryptionTest < MiniTest::Test ENCRYPT_ZIP_TEST_FILE = 'test/data/zipWithEncryption.zip' INPUT_FILE1 = 'test/data/file1.txt' + INPUT_FILE2 = 'test/data/file2.txt' def setup Zip.default_compression = ::Zlib::DEFAULT_COMPRESSION @@ -33,7 +34,7 @@ def test_encrypt ) do |zis| entry = zis.get_next_entry assert_equal test_filename, entry.name - assert_equal 1327, entry.size + assert_equal 1_327, entry.size assert_equal content, zis.read end @@ -55,8 +56,12 @@ def test_decrypt ) do |zis| entry = zis.get_next_entry assert_equal 'file1.txt', entry.name - assert_equal 1327, entry.size + assert_equal 1_327, entry.size assert_equal ::File.open(INPUT_FILE1, 'r').read, zis.read + entry = zis.get_next_entry + assert_equal 'file2.txt', entry.name + assert_equal 41_234, entry.size + assert_equal ::File.open(INPUT_FILE2, 'r').read, zis.read end end end diff --git a/test/stored_support_test.rb b/test/stored_support_test.rb index 16f1bed2..4e0cdad6 100644 --- a/test/stored_support_test.rb +++ b/test/stored_support_test.rb @@ -4,7 +4,8 @@ class StoredSupportTest < MiniTest::Test STORED_ZIP_TEST_FILE = 'test/data/zipWithStoredCompression.zip' - ENCRYPTED_STORED_ZIP_TEST_FILE = 'test/data/zipWithStoredCompressionAndEncryption.zip' + ENCRYPTED_STORED_ZIP_TEST_FILE = + 'test/data/zipWithStoredCompressionAndEncryption.zip' INPUT_FILE1 = 'test/data/file1.txt' INPUT_FILE2 = 'test/data/file2.txt' From 19e5f4a8ce726b1498eb0f34f778f33708216145 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 27 Jun 2021 21:43:03 +0100 Subject: [PATCH 347/469] Detect and raise GPFBit3Error in `InputStream.get_next_entry`. We were previously trying to work out where the next entry would be, even with GP bit 3 set, but the logic was flaky and cannot really be correct given the data available. It's not expected behaviour, so raise the error instead. This means that we get rid of the incorrect `Entry.data_descriptor_size` which was doing more harm than good. --- lib/zip/entry.rb | 6 +----- lib/zip/input_stream.rb | 12 +++++++++++- test/data/gpbit3stored.zip | Bin 132 -> 9604 bytes test/file_test.rb | 6 +++++- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 83ced75e..e87f3362 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -215,7 +215,7 @@ def cdir_header_size #:nodoc:all end def next_header_offset #:nodoc:all - local_entry_offset + compressed_size + data_descriptor_size + local_entry_offset + compressed_size end # Extracts entry to file dest_path (defaults to @name). @@ -723,10 +723,6 @@ def parse_zip64_extra(for_local_header) #:nodoc:all end end - def data_descriptor_size - (@gp_flags & 0x0008) > 0 ? 16 : 0 - end - # For DEFLATED compression *only*: set the general purpose flags 1 and 2 to # indicate compression level. This seems to be mainly cosmetic but they are # generally set by other tools - including in docx files. It is these flags diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index 00b1d99b..5a9c289f 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -69,7 +69,17 @@ def close # the first entry in the archive. Returns nil when there are # no more entries. def get_next_entry - @archive_io.seek(@current_entry.next_header_offset, IO::SEEK_SET) if @current_entry + unless @current_entry.nil? + if @current_entry.incomplete? + raise GPFBit3Error, + 'It is not possible to get complete info from the local ' \ + 'header to extract this entry (GP flags bit 3 is set). ' \ + 'Please use `Zip::File` instead of `Zip::InputStream`.' + end + + @archive_io.seek(@current_entry.next_header_offset, IO::SEEK_SET) + end + open_entry end diff --git a/test/data/gpbit3stored.zip b/test/data/gpbit3stored.zip index 3c73eeb3bc4564638443ea75dcc6fcf46cc856bd..036424af85f5732d0efe68c6a08ac562d66af1c7 100644 GIT binary patch literal 9604 zcmai)V{j#2m+s@_#I|kQww>W$kgW>#--`tr3=9B92xe(xZ^6dw?(MFn2>^raQ!Fuz zR$eyt@J0XwhdO}-1N+Y{LUYa$#EKcP^%2X!g#zM!36{$k&S?@aR)VTXkFptME7SF` z$0YFBT%Os1LPW?eXdO8wyb|ITedsh0krvT@^=(X8R>9lai?avQ?r^|AoZwymHpuGG zFVYFqx_)_;c0$%Mv;!?l&)htg_Tq=OWa<%;IU}ll|P&IfY7=()Murhy8ZYwsrsDf9`@Qb?dvgUtN-_i;^$ir zuuM`Oi0f4B6|#;i&;#HUp#YYWMmD?CJe+C#J|4Iv$b00+5Gpz^0{YHA;^kqTM;y1~ ztIJ63qHz}5x8_MTL#_yo!mFCzKH2vWNJ_-;Smm`t&;lg6IQ z&WXkL85N^ntO?^W^;5`Qn{*DBk_@I_t(ucLg}|TU4Iy>-C!~+XOBueJyF7D+HT;r# zb-Hn%Ej0r5sUNQ#wVw3E;X@BA@*za&|J5ewMgLLVFe5+3qEosG8%ER)N{1dDiK%-ro-t8DwHIZ?D58YJ^zk#3M~}QJ@CCZ zJgQBI-W)Qm*S%oIJ_)-ViR1JUgfAZ9d=pI_I%!dV-TnP}8kg26yh@|jP?#Yv{?!GC zAx~`jmmIz^>TRL?=FOxOU3!0&yzpS=rd^#6F>TlzKOAt&o?~fP%AWsfBabX?llii5 zk7~fu(8yTy=?`~#faCf$v)||Jgc9`lo~{k&vlB{vYC0%qp&>%CEcZ@^Xf5v#Q3Ia6 zV2=+QjQB3tkYdwr-^c}5Ouz~U_6Q%PnAzeO%=i*K4@@yD;*;@R zvPzgUV$LF=8SRcxciK9=bhgVnm5DZ;r&n4U!`i-Kh7+DXmL5WLL_Xz=^U|!Ms z2_&+vQ?i|1ayHoOeSLC1*9~4s$DAsQ%FGVIfyX$Uo}h06=2Tq9^Him2y*{EBcs72 z^$HtPQsH+p4Z~MN@chYZ>14DB*Esu`P)$RRYxYUVVK`a#tnbfGB+}FOu6$#V4V_SemN`L%zGe!RQ95ggz)6_FR#mpbgUOdkaS`T61^vT^8g$qOeTE1d-=ycPwbtlncGg+lZVt zE&l#o-#rCK7YO^5-LVqRo)5>SMOIEUdX8dIK?c`Ov~aqv3wk&+UJ*&39fAFYcaI5@ zJDA}j>zi3{EN2oynns2#|Jb@YctEkv?ya8Rs1897%g~KC$s?5P6S0OVqsAhae#^md z5D14l�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 literal 132 zcmWIWW@Zs#;9y{22(4-M0a9?l4rHa}=j)YJlmIEN0B?4V6{$vbEI?rp4)A7V5@AMY fMV13;g@G-NAQsf10B=?{5SI}MO@Xu}h{FH?7~2w0 diff --git a/test/file_test.rb b/test/file_test.rb index c183c26d..6daf97d6 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -45,7 +45,11 @@ def test_create_from_scratch def test_get_input_stream_stored_with_gpflag_bit3 ::Zip::File.open('test/data/gpbit3stored.zip') do |zf| - assert_equal("foo\n", zf.read('foo.txt')) + zis = zf.get_input_stream('file1.txt') + assert_raises(::Zip::GPFBit3Error) do + zis.get_next_entry + end + zf.get_input_stream('file2.txt') end end From 66527ae10d86762b803964e3e6928f4ec7f7d962 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 27 Jun 2021 21:55:15 +0100 Subject: [PATCH 348/469] Fix minor typo in `GPFBit3Error` message in `InputStream`. --- lib/zip/input_stream.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index 5a9c289f..66ffdd3e 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -153,7 +153,7 @@ def open_entry && !@complete_entry raise GPFBit3Error, 'It is not possible to get complete info from the local ' \ - 'header to extract this entry (GP flags bit 3 is set).' \ + 'header to extract this entry (GP flags bit 3 is set). ' \ 'Please use `Zip::File` instead of `Zip::InputStream`.' end From 322955c6b4cdd0f0dad336a5df4ddbd340372cfa Mon Sep 17 00:00:00 2001 From: Yo Yehudi Date: Sun, 27 Jun 2021 16:05:17 +0100 Subject: [PATCH 349/469] add research notice so that contributors will be aware metrics are being gathered on this repo over the next year. If there's a better place to put this please let me know. :) --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 9fa7d5bb..f95a825e 100644 --- a/README.md +++ b/README.md @@ -413,3 +413,14 @@ See https://github.com/rubyzip/rubyzip/graphs/contributors for a comprehensive l Rubyzip is distributed under the same license as ruby. See http://www.ruby-lang.org/en/LICENSE.txt + +## Research notice +Please note that this repository is participating in a study into sustainability + of open source projects. Data will be gathered about this repository for + approximately the next 12 months, starting from June 2021. + +Data collected will include number of contributors, number of PRs, time taken to + close/merge these PRs, and issues closed. + +For more information, please visit +[our informational page](https://sustainable-open-science-and-software.github.io/) or download our [participant information sheet](https://sustainable-open-science-and-software.github.io/assets/PIS_sustainable_software.pdf). From 54b7762c8f481e03b733ff053750cddc7ccf13ac Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 30 Jun 2021 23:18:59 +0100 Subject: [PATCH 350/469] Don't silently alter zip files opened with `Zip::sort_entries`. Fixes #329. --- Changelog.md | 1 + README.md | 6 +++--- lib/zip/entry_set.rb | 6 ++---- test/entry_set_test.rb | 14 ++++++++++++++ 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/Changelog.md b/Changelog.md index ecabf38c..03971378 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,6 @@ # 3.0.0 (Next) +- Don't silently alter zip files opened with `Zip::sort_entries`. [#329](https://github.com/rubyzip/rubyzip/issues/329) - Use named parameters for optional arguments in the public API. - Raise an error if entry names exceed 65,535 characters. [#247](https://github.com/rubyzip/rubyzip/issues/247) - Remove the `ZipXError` v1 legacy classes. diff --git a/README.md b/README.md index f95a825e..e5ddf6f2 100644 --- a/README.md +++ b/README.md @@ -132,9 +132,9 @@ class ZipFileGenerator end ``` -### Save zip archive entries in sorted by name state +### Save zip archive entries sorted by name -To save zip archives in sorted order like below, you need to set `::Zip.sort_entries` to `true` +To save zip archives with their entries sorted by name (see below), set `::Zip.sort_entries` to `true` ``` Vegetable/ @@ -148,7 +148,7 @@ fruit/mango fruit/orange ``` -After this, entries in the zip archive will be saved in ordered state. +Opening an existing zip file with this option set will not change the order of the entries automatically. Altering the zip file - adding an entry, renaming an entry, adding or changing the archive comment, etc - will cause the ordering to be applied when closing the file. ### Default permissions of zip archives diff --git a/lib/zip/entry_set.rb b/lib/zip/entry_set.rb index 530ef7ef..a771dc7f 100644 --- a/lib/zip/entry_set.rb +++ b/lib/zip/entry_set.rb @@ -35,10 +35,8 @@ def delete(entry) entry if @entry_set.delete(to_key(entry)) end - def each - @entry_set = sorted_entries.dup.each do |_, value| - yield(value) - end + def each(&block) + entries.each(&block) end def entries diff --git a/test/entry_set_test.rb b/test/entry_set_test.rb index fa91b77d..42e93681 100644 --- a/test/entry_set_test.rb +++ b/test/entry_set_test.rb @@ -62,9 +62,15 @@ def test_each count = 0 @zip_entry_set.each do |entry| assert(ZIP_ENTRIES.include?(entry)) + refute(entry.dirty) + entry.dirty = true # Check that entries can be changed in this block. count += 1 end + assert_equal(ZIP_ENTRIES.size, count) + @zip_entry_set.each do |entry| + assert(entry.dirty) + end end def test_entries @@ -101,6 +107,14 @@ def test_entries_sorted_in_each arr << entry end assert_equal(ZIP_ENTRIES.sort, arr) + + # Ensure `each` above hasn't permanently altered the ordering. + ::Zip.sort_entries = false + arr = [] + @zip_entry_set.each do |entry| + arr << entry + end + assert_equal(ZIP_ENTRIES, arr) end def test_compound From c3b1e5d69370ccba494c68387da78e2e3411bd21 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 3 Jul 2021 13:45:22 +0100 Subject: [PATCH 351/469] Pick changes from v2.3.1. --- Changelog.md | 4 ++++ lib/zip.rb | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/Changelog.md b/Changelog.md index 03971378..f1dd25f8 100644 --- a/Changelog.md +++ b/Changelog.md @@ -38,6 +38,10 @@ Tooling/internal: - Update rubocop again and run it in CI. [#444](https://github.com/rubyzip/rubyzip/pull/444) - Fix a test that was incorrect on big-endian architectures. [#445](https://github.com/rubyzip/rubyzip/pull/445) +# 2.3.1 (2021-07-03) + +- A "dummy" release to warn about breaking changes coming in version 3.0. + # 2.3.0 (2020-03-14) - Fix frozen string literal error [#431](https://github.com/rubyzip/rubyzip/pull/431) diff --git a/lib/zip.rb b/lib/zip.rb index 87a236a8..98d5c8f4 100644 --- a/lib/zip.rb +++ b/lib/zip.rb @@ -36,6 +36,8 @@ require 'zip/errors' module Zip + BANNER = [].freeze + extend self attr_accessor :unicode_names, :on_exists_proc, @@ -55,6 +57,8 @@ module Zip }.freeze def reset! + warn BANNER.join("\n") unless BANNER.empty? + @_ran_once = false @unicode_names = false @on_exists_proc = false From bc6523ec43e911d7931aafbda2a81dae68617a90 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 5 Jul 2021 22:22:01 +0100 Subject: [PATCH 352/469] Unpick changes from v2.3.1. --- Changelog.md | 4 ++++ lib/zip.rb | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Changelog.md b/Changelog.md index f1dd25f8..84b45492 100644 --- a/Changelog.md +++ b/Changelog.md @@ -38,6 +38,10 @@ Tooling/internal: - Update rubocop again and run it in CI. [#444](https://github.com/rubyzip/rubyzip/pull/444) - Fix a test that was incorrect on big-endian architectures. [#445](https://github.com/rubyzip/rubyzip/pull/445) +# 2.3.2 (2021-07-05) + +- A "dummy" release to warn about breaking changes coming in version 3.0. This updated version uses the Gem `post_install_message` instead of printing to `STDERR`. + # 2.3.1 (2021-07-03) - A "dummy" release to warn about breaking changes coming in version 3.0. diff --git a/lib/zip.rb b/lib/zip.rb index 98d5c8f4..87a236a8 100644 --- a/lib/zip.rb +++ b/lib/zip.rb @@ -36,8 +36,6 @@ require 'zip/errors' module Zip - BANNER = [].freeze - extend self attr_accessor :unicode_names, :on_exists_proc, @@ -57,8 +55,6 @@ module Zip }.freeze def reset! - warn BANNER.join("\n") unless BANNER.empty? - @_ran_once = false @unicode_names = false @on_exists_proc = false From f7cd692e15d80addc7abeb7bfde643e1649a1390 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Fri, 19 Nov 2021 19:35:36 +0000 Subject: [PATCH 353/469] Fix reading zip files with max length file comment. If a zip file has a comment that is 65,535 characters long - which is a valid length and the maximum allowable length - the initial read of the archive fails to find the End of Central Directory Record and therefore cannot read the rest of the file. This commit fixes this by making sure that we look far enough back into the file from the end to find the EoCDR. Test added to catch regressions. Fixes #508. --- lib/zip/central_directory.rb | 2 +- test/data/max_length_file_comment.zip | Bin 0 -> 71396 bytes test/file_test.rb | 5 +++++ 3 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 test/data/max_length_file_comment.zip diff --git a/lib/zip/central_directory.rb b/lib/zip/central_directory.rb index 51d72b37..29fb2d0d 100644 --- a/lib/zip/central_directory.rb +++ b/lib/zip/central_directory.rb @@ -7,9 +7,9 @@ class CentralDirectory END_OF_CDS = 0x06054b50 ZIP64_END_OF_CDS = 0x06064b50 ZIP64_EOCD_LOCATOR = 0x07064b50 - MAX_END_OF_CDS_SIZE = 65_536 + 18 STATIC_EOCD_SIZE = 22 ZIP64_STATIC_EOCD_SIZE = 56 + MAX_END_OF_CDS_SIZE = 65_535 + STATIC_EOCD_SIZE attr_reader :comment diff --git a/test/data/max_length_file_comment.zip b/test/data/max_length_file_comment.zip new file mode 100644 index 0000000000000000000000000000000000000000..dc39d04ad831580547a1f53a3413994398e6150f GIT binary patch literal 71396 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 literal 0 HcmV?d00001 diff --git a/test/file_test.rb b/test/file_test.rb index 6daf97d6..6d8bc95b 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -181,6 +181,11 @@ def test_open_buffer_without_block assert zf.entries.map(&:name).include?('zippedruby1.rb') end + def test_open_file_with_max_length_comment + # Should not raise any errors. + Zip::File.open('test/data/max_length_file_comment.zip') + end + def test_cleans_up_tempfiles_after_close zf = ::Zip::File.new(EMPTY_FILENAME, create: true) zf.get_output_stream('myFile') do |os| From 765cb316f15a542a8eb7393fc49a5e9d0801e55d Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 16 Nov 2021 21:42:11 +0000 Subject: [PATCH 354/469] Fix reading unknown extra fields. When loading extra fields from both the central directory and local headers, unknown fields were not merged correctly. They were being appended, which means that we end up with the two versions stuck together - in some cases duplicating the field completely. This broke all kinds of things (like calculating the size of a local header) in subtle ways. This commit fixes this by implementing a new `Unknown` extra field type, and making sure that when reading local and central extra fields they are stored and preserved correctly. We cannot assume the unknown fields use the same data in the local and central headers. Fixes #505. --- .rubocop_todo.yml | 1 + lib/zip/central_directory.rb | 2 +- lib/zip/entry.rb | 8 ++--- lib/zip/extra_field.rb | 31 ++++++++----------- lib/zip/extra_field/unknown.rb | 33 +++++++++++++++++++++ test/extra_field_test.rb | 34 +++++++++++++++++---- test/extra_field_unknown_test.rb | 51 ++++++++++++++++++++++++++++++++ test/local_entry_test.rb | 12 ++++++-- 8 files changed, 139 insertions(+), 33 deletions(-) create mode 100644 lib/zip/extra_field/unknown.rb create mode 100644 test/extra_field_unknown_test.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 1afa96b1..b5466f40 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -61,6 +61,7 @@ Style/ClassAndModuleChildren: - 'lib/zip/extra_field/old_unix.rb' - 'lib/zip/extra_field/universal_time.rb' - 'lib/zip/extra_field/unix.rb' + - 'lib/zip/extra_field/unknown.rb' - 'lib/zip/extra_field/zip64.rb' - 'lib/zip/extra_field/zip64_placeholder.rb' diff --git a/lib/zip/central_directory.rb b/lib/zip/central_directory.rb index 29fb2d0d..4979ea6d 100644 --- a/lib/zip/central_directory.rb +++ b/lib/zip/central_directory.rb @@ -174,7 +174,7 @@ def read_central_directory_entries(io) #:nodoc: unless offset.nil? io_save = io.tell io.seek(offset, IO::SEEK_SET) - entry.read_extra_field(read_local_extra_field(io)) + entry.read_extra_field(read_local_extra_field(io), local: true) io.seek(io_save, IO::SEEK_SET) end diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index e87f3362..d1241e56 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -313,7 +313,7 @@ def read_local_entry(io) #:nodoc:all raise ::Zip::Error, 'Truncated local zip entry header' end - read_extra_field(extra) + read_extra_field(extra, local: true) parse_zip64_extra(true) @local_header_size = calculate_local_header_size end @@ -417,11 +417,11 @@ def check_c_dir_entry_comment_size raise ::Zip::Error, 'Truncated cdir zip entry header' end - def read_extra_field(buf) + def read_extra_field(buf, local: false) if @extra.kind_of?(::Zip::ExtraField) - @extra.merge(buf) if buf + @extra.merge(buf, local: local) if buf else - @extra = ::Zip::ExtraField.new(buf) + @extra = ::Zip::ExtraField.new(buf, local: local) end end diff --git a/lib/zip/extra_field.rb b/lib/zip/extra_field.rb index 24352aae..658675b4 100644 --- a/lib/zip/extra_field.rb +++ b/lib/zip/extra_field.rb @@ -4,8 +4,8 @@ module Zip class ExtraField < Hash ID_MAP = {} - def initialize(binstr = nil) - merge(binstr) if binstr + def initialize(binstr = nil, local: false) + merge(binstr, local: local) if binstr end def extra_field_type_exist(binstr, id, len, index) @@ -18,25 +18,18 @@ def extra_field_type_exist(binstr, id, len, index) end end - def extra_field_type_unknown(binstr, len, index) - create_unknown_item unless self['Unknown'] + def extra_field_type_unknown(binstr, len, index, local) + self['Unknown'] ||= Unknown.new + if !len || len + 4 > binstr[index..-1].bytesize - self['Unknown'] << binstr[index..-1] + self['Unknown'].merge(binstr[index..-1], local: local) return end - self['Unknown'] << binstr[index, len + 4] - end - def create_unknown_item - s = +'' - class << s - alias_method :to_c_dir_bin, :to_s - alias_method :to_local_bin, :to_s - end - self['Unknown'] = s + self['Unknown'].merge(binstr[index, len + 4], local: local) end - def merge(binstr) + def merge(binstr, local: false) return if binstr.empty? i = 0 @@ -46,8 +39,7 @@ def merge(binstr) if id && ID_MAP.member?(id) extra_field_type_exist(binstr, id, len, i) elsif id - create_unknown_item unless self['Unknown'] - break unless extra_field_type_unknown(binstr, len, i) + break unless extra_field_type_unknown(binstr, len, i, local) end i += len + 4 end @@ -61,8 +53,8 @@ def create(name) self[name] = field_class.new end - # place Unknown last, so "extra" data that is missing the proper signature/size - # does not prevent known fields from being read back in + # Place Unknown last, so "extra" data that is missing the proper + # signature/size does not prevent known fields from being read back in. def ordered_values result = [] each { |k, v| k == 'Unknown' ? result.push(v) : result.unshift(v) } @@ -92,6 +84,7 @@ def local_size end end +require 'zip/extra_field/unknown' require 'zip/extra_field/generic' require 'zip/extra_field/universal_time' require 'zip/extra_field/old_unix' diff --git a/lib/zip/extra_field/unknown.rb b/lib/zip/extra_field/unknown.rb new file mode 100644 index 00000000..84e87f34 --- /dev/null +++ b/lib/zip/extra_field/unknown.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Zip + # A class to hold unknown extra fields so that they are preserved. + class ExtraField::Unknown + def initialize + @local_bin = +'' + @cdir_bin = +'' + end + + def merge(binstr, local: false) + return if binstr.empty? + + if local + @local_bin << binstr + else + @cdir_bin << binstr + end + end + + def to_local_bin + @local_bin + end + + def to_c_dir_bin + @cdir_bin + end + + def ==(other) + @local_bin == other.to_local_bin && @cdir_bin == other.to_c_dir_bin + end + end +end diff --git a/test/extra_field_test.rb b/test/extra_field_test.rb index 7aea91e5..c91a7bc6 100644 --- a/test/extra_field_test.rb +++ b/test/extra_field_test.rb @@ -6,17 +6,29 @@ class ZipExtraFieldTest < MiniTest::Test def test_new extra_pure = ::Zip::ExtraField.new('') extra_withstr = ::Zip::ExtraField.new('foo') + extra_withstr_local = ::Zip::ExtraField.new('foo', local: true) + assert_instance_of(::Zip::ExtraField, extra_pure) assert_instance_of(::Zip::ExtraField, extra_withstr) + assert_instance_of(::Zip::ExtraField, extra_withstr_local) + + assert_equal('foo', extra_withstr['Unknown'].to_c_dir_bin) + assert_equal('foo', extra_withstr_local['Unknown'].to_local_bin) end def test_unknownfield extra = ::Zip::ExtraField.new('foo') - assert_equal(extra['Unknown'], 'foo') + assert_equal('foo', extra['Unknown'].to_c_dir_bin) + extra.merge('a') - assert_equal(extra['Unknown'], 'fooa') + assert_equal('fooa', extra['Unknown'].to_c_dir_bin) + extra.merge('barbaz') - assert_equal(extra.to_s, 'fooabarbaz') + assert_equal('fooabarbaz', extra['Unknown'].to_c_dir_bin) + + extra.merge('bar', local: true) + assert_equal('bar', extra['Unknown'].to_local_bin) + assert_equal('fooabarbaz', extra['Unknown'].to_c_dir_bin) end def test_bad_header_id @@ -66,9 +78,9 @@ def test_to_s extra = ::Zip::ExtraField.new(str) assert_instance_of(String, extra.to_s) - s = extra.to_s - extra.merge('foo') - assert_equal(s.length + 3, extra.to_s.length) + extra_len = extra.to_s.length + extra.merge('foo', local: true) + assert_equal(extra_len + 3, extra.to_s.length) end def test_equality @@ -99,4 +111,14 @@ def test_read_local_extra_field end end end + + def test_load_unknown_extra_field + ::Zip::File.open('test/data/osx-archive.zip') do |zf| + zf.each do |entry| + # Check that there is only one occurance of the 'ux' extra field. + assert_equal(0, entry.extra['Unknown'].to_c_dir_bin.rindex('ux')) + assert_equal(0, entry.extra['Unknown'].to_local_bin.rindex('ux')) + end + end + end end diff --git a/test/extra_field_unknown_test.rb b/test/extra_field_unknown_test.rb new file mode 100644 index 00000000..4d24d928 --- /dev/null +++ b/test/extra_field_unknown_test.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require 'test_helper' + +class ZipExtraFieldUnknownTest < MiniTest::Test + def test_new + extra = ::Zip::ExtraField::Unknown.new + assert_empty(extra.to_c_dir_bin) + assert_empty(extra.to_local_bin) + end + + def test_merge_cdir_then_local + extra = ::Zip::ExtraField::Unknown.new + field = "ux\v\x00\x01\x04\xF6\x01\x00\x00\x04\x14\x00\x00\x00" + + extra.merge(field) + assert_empty(extra.to_local_bin) + assert_equal(field, extra.to_c_dir_bin) + + extra.merge(field, local: true) + assert_equal(field, extra.to_local_bin) + assert_equal(field, extra.to_c_dir_bin) + end + + def test_merge_local_only + extra = ::Zip::ExtraField::Unknown.new + field = "ux\v\x00\x01\x04\xF6\x01\x00\x00\x04\x14\x00\x00\x00" + + extra.merge(field, local: true) + assert_equal(field, extra.to_local_bin) + assert_empty(extra.to_c_dir_bin) + end + + def test_equality + extra1 = ::Zip::ExtraField::Unknown.new + extra2 = ::Zip::ExtraField::Unknown.new + assert_equal(extra1, extra2) + + extra1.merge("ux\v\x00\x01\x04\xF6\x01\x00\x00\x04\x14\x00\x00\x00") + refute_equal(extra1, extra2) + + extra2.merge("ux\v\x00\x01\x04\xF6\x01\x00\x00\x04\x14\x00\x00\x00") + assert_equal(extra1, extra2) + + extra1.merge('foo', local: true) + refute_equal(extra1, extra2) + + extra2.merge('foo', local: true) + assert_equal(extra1, extra2) + end +end diff --git a/test/local_entry_test.rb b/test/local_entry_test.rb index 15c367a7..3cf21e62 100644 --- a/test/local_entry_test.rb +++ b/test/local_entry_test.rb @@ -83,6 +83,7 @@ def test_write_entry_with_zip64 'file.zip', 'entry_name', comment: 'my little comment', size: 400, extra: 'thisIsSomeExtraInformation', compressed_size: 100, crc: 987_654 ) + entry.extra.merge('thisIsSomeExtraInformation', local: true) write_to_file(LEH_FILE, CEH_FILE, entry) local_entry, central_entry = read_from_file(LEH_FILE, CEH_FILE) @@ -153,18 +154,23 @@ def test_read64_local_offset private - def compare_local_entry_headers(entry1, entry2) + def compare_common_entry_headers(entry1, entry2) assert_equal(entry1.compressed_size, entry2.compressed_size) assert_equal(entry1.crc, entry2.crc) - assert_equal(entry1.extra, entry2.extra) assert_equal(entry1.compression_method, entry2.compression_method) assert_equal(entry1.name, entry2.name) assert_equal(entry1.size, entry2.size) assert_equal(entry1.local_header_offset, entry2.local_header_offset) end + def compare_local_entry_headers(entry1, entry2) + compare_common_entry_headers(entry1, entry2) + assert_equal(entry1.extra.to_local_bin, entry2.extra.to_local_bin) + end + def compare_c_dir_entry_headers(entry1, entry2) - compare_local_entry_headers(entry1, entry2) + compare_common_entry_headers(entry1, entry2) + assert_equal(entry1.extra.to_c_dir_bin, entry2.extra.to_c_dir_bin) assert_equal(entry1.comment, entry2.comment) end From 6a516fb0b1045b655ad63c4acaf29c7603708fff Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 20 Nov 2021 10:36:32 +0000 Subject: [PATCH 355/469] Factor out reading EOCD records. This allows for reading the EOCD records without then automatically reading all of the entry data as well, so that we can do other things faster, like provide the number of entries in an archive. --- lib/zip/central_directory.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/zip/central_directory.rb b/lib/zip/central_directory.rb index 4979ea6d..d7d0cf25 100644 --- a/lib/zip/central_directory.rb +++ b/lib/zip/central_directory.rb @@ -194,10 +194,14 @@ def read_local_extra_field(io) end def read_from_stream(io) #:nodoc: + read_eocds(io) + read_central_directory_entries(io) + end + + def read_eocds(io) #:nodoc: buf = start_buf(io) unpack_64_e_o_c_d(buf) if zip64_file?(buf) unpack_e_o_c_d(buf) - read_central_directory_entries(io) end def zip64_file?(buf) From 3db1eff1e32a6bc7014ad95801f1dfe595040938 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 20 Nov 2021 10:50:55 +0000 Subject: [PATCH 356/469] Add `CentralDirectory#count_entries`. This method gets the number of entries from a zip archive without loading all of the individual entries. --- lib/zip/central_directory.rb | 9 +++++++++ test/central_directory_test.rb | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/lib/zip/central_directory.rb b/lib/zip/central_directory.rb index d7d0cf25..e45fc030 100644 --- a/lib/zip/central_directory.rb +++ b/lib/zip/central_directory.rb @@ -228,6 +228,15 @@ def size @entry_set.size end + # Reads the End of Central Directory Record (and the Zip64 equivalent if + # needs be) and returns the number of entries in the archive. This is a + # convenience method that avoids reading in all of the entry data to get a + # very quick entry count. + def count_entries(io) + read_eocds(io) + @size + end + def self.read_from_stream(io) #:nodoc: cdir = new cdir.read_from_stream(io) diff --git a/test/central_directory_test.rb b/test/central_directory_test.rb index afe2156e..97b5c482 100644 --- a/test/central_directory_test.rb +++ b/test/central_directory_test.rb @@ -44,6 +44,24 @@ def test_read_eocd_with_wrong_cdir_offset_from_buffer end end + def test_count_entries + [ + ['test/data/osx-archive.zip', 4], + ['test/data/zip64-sample.zip', 2], + ['test/data/max_length_file_comment.zip', 1] + ].each do |filename, num_entries| + cdir = ::Zip::CentralDirectory.new + + ::File.open(filename, 'rb') do |f| + assert_equal(num_entries, cdir.count_entries(f)) + + f.seek(0) + s = StringIO.new(f.read) + assert_equal(num_entries, cdir.count_entries(s)) + end + end + end + def test_write_to_stream entries = [ ::Zip::Entry.new( From 22e47641e61aac923b1e7bcb3fb653808000b006 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 20 Nov 2021 10:53:00 +0000 Subject: [PATCH 357/469] Add `File::count_entries`. This method provides a short cut to finding out how many entries are in an archive by reading this number directly from the central directory, and not iterating through the entire set of entries. --- lib/zip/file.rb | 14 ++++++++++++++ test/file_test.rb | 18 ++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 576639b5..dfe9758b 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -171,6 +171,20 @@ def foreach(zip_file_name, &block) zip_file.each(&block) end end + + # Count the entries in a zip archive without reading the whole set of + # entry data into memory. + def count_entries(path_or_io) + cdir = ::Zip::CentralDirectory.new + + if path_or_io.kind_of?(String) + ::File.open(path_or_io, 'rb') do |f| + cdir.count_entries(f) + end + else + cdir.count_entries(path_or_io) + end + end end # Returns an input stream to the specified entry. If a block is passed diff --git a/test/file_test.rb b/test/file_test.rb index 6d8bc95b..13e98705 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -186,6 +186,24 @@ def test_open_file_with_max_length_comment Zip::File.open('test/data/max_length_file_comment.zip') end + def test_count_entries + [ + ['test/data/osx-archive.zip', 4], + ['test/data/zip64-sample.zip', 2], + ['test/data/max_length_file_comment.zip', 1] + ].each do |filename, num_entries| + assert_equal(num_entries, ::Zip::File.count_entries(filename)) + + ::File.open(filename, 'rb') do |f| + assert_equal(num_entries, ::Zip::File.count_entries(f)) + + f.seek(0) + s = StringIO.new(f.read) + assert_equal(num_entries, ::Zip::File.count_entries(s)) + end + end + end + def test_cleans_up_tempfiles_after_close zf = ::Zip::File.new(EMPTY_FILENAME, create: true) zf.get_output_stream('myFile') do |os| From f5e19db2736ab3daab29e09f3638386b989c1b48 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 20 Nov 2021 20:02:47 +0000 Subject: [PATCH 358/469] Add a 100,000 file zip to test `count_entries`. --- test/central_directory_test.rb | 3 ++- test/data/100000-files.zip | Bin 0 -> 9377888 bytes test/file_test.rb | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 test/data/100000-files.zip diff --git a/test/central_directory_test.rb b/test/central_directory_test.rb index 97b5c482..88258caa 100644 --- a/test/central_directory_test.rb +++ b/test/central_directory_test.rb @@ -48,7 +48,8 @@ def test_count_entries [ ['test/data/osx-archive.zip', 4], ['test/data/zip64-sample.zip', 2], - ['test/data/max_length_file_comment.zip', 1] + ['test/data/max_length_file_comment.zip', 1], + ['test/data/100000-files.zip', 100_000] ].each do |filename, num_entries| cdir = ::Zip::CentralDirectory.new diff --git a/test/data/100000-files.zip b/test/data/100000-files.zip new file mode 100644 index 0000000000000000000000000000000000000000..ee3751ba3c69d08bc28e316c349fa4019db8c8bf GIT binary patch literal 9377888 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 B Date: Tue, 30 Nov 2021 22:22:37 +0000 Subject: [PATCH 359/469] Ensure `File.open_buffer` doesn't rewrite unchanged data. --- .rubocop_todo.yml | 2 +- lib/zip/file.rb | 3 +++ test/file_test.rb | 26 ++++++++++++++++++++++++-- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b5466f40..4406c97d 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -30,7 +30,7 @@ Metrics/CyclomaticComplexity: # Offense count: 47 # Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods. Metrics/MethodLength: - Max: 32 + Max: 34 # Offense count: 5 # Configuration parameters: CountKeywordArgs. diff --git a/lib/zip/file.rb b/lib/zip/file.rb index dfe9758b..ddb5e736 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -90,6 +90,7 @@ def initialize(path_or_io, create: false, buffer: false, **options) end elsif buffer && path_or_io.size > 0 # This zip is probably a non-empty StringIO. + @create = false read_from_stream(path_or_io) elsif @create # This zip is completely new/empty and is to be created. @@ -311,6 +312,8 @@ def commit # Write buffer write changes to buffer and return def write_buffer(io = ::StringIO.new) + return unless commit_required? + ::Zip::OutputStream.write_buffer(io) do |zos| @entry_set.each { |e| e.write_to_zip_output_stream(zos) } zos.comment = comment diff --git a/test/file_test.rb b/test/file_test.rb index cf3619fc..d08778aa 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -111,17 +111,27 @@ def test_get_output_stream end def test_open_buffer_with_string - string = File.read('test/data/rubycode.zip', mode: 'rb') + data = File.read('test/data/rubycode.zip', mode: 'rb') + string = data.dup + ::Zip::File.open_buffer string do |zf| assert zf.entries.map(&:name).include?('zippedruby1.rb') end + + # Ensure the buffer hasn't changed. + assert_equal(data, string) end def test_open_buffer_with_stringio - string_io = StringIO.new File.read('test/data/rubycode.zip', mode: 'rb') + data = File.read('test/data/rubycode.zip', mode: 'rb') + string_io = StringIO.new(data.dup) + ::Zip::File.open_buffer string_io do |zf| assert zf.entries.map(&:name).include?('zippedruby1.rb') end + + # Ensure the buffer hasn't changed. + assert_equal(data, string_io.string) end def test_close_buffer_with_stringio @@ -181,6 +191,18 @@ def test_open_buffer_without_block assert zf.entries.map(&:name).include?('zippedruby1.rb') end + def test_open_buffer_without_block_write_buffer_does_nothing + data = File.read('test/data/rubycode.zip', mode: 'rb') + string_io = StringIO.new(data.dup) + + zf = ::Zip::File.open_buffer(string_io) + assert zf.entries.map(&:name).include?('zippedruby1.rb') + + # Ensure the buffer isn't changed. + zf.write_buffer(string_io) + assert_equal(data, string_io.string) + end + def test_open_file_with_max_length_comment # Should not raise any errors. Zip::File.open('test/data/max_length_file_comment.zip') From 1c33f2dd90f0fb97b2e63d4a124912b98b7c06e4 Mon Sep 17 00:00:00 2001 From: Taichi Ishitani Date: Wed, 29 Dec 2021 22:36:13 +0900 Subject: [PATCH 360/469] add Ruby 3.1 to CI --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8b2abc0e..097b8aa8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,7 +8,7 @@ jobs: fail-fast: false matrix: os: [ubuntu] - ruby: [2.4, 2.5, 2.6, 2.7, '3.0', head, jruby, jruby-head, truffleruby, truffleruby-head] + ruby: [2.4, 2.5, 2.6, 2.7, '3.0', '3.1', head, jruby, jruby-head, truffleruby, truffleruby-head] include: - os: macos ruby: 2.4 From 8f743d7f6853c12087ba702dd4064f2ff642f80b Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 2 Jan 2022 13:09:06 +0000 Subject: [PATCH 361/469] Make ruby versions list in the CI consistent. Ruby version `3.0` must be quoted otherwise it's interpreted as `3`. Might as well make the rest in the list consistent. --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 097b8aa8..338de224 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,7 +8,7 @@ jobs: fail-fast: false matrix: os: [ubuntu] - ruby: [2.4, 2.5, 2.6, 2.7, '3.0', '3.1', head, jruby, jruby-head, truffleruby, truffleruby-head] + ruby: ['2.4', '2.5', '2.6', '2.7', '3.0', '3.1', head, jruby, jruby-head, truffleruby, truffleruby-head] include: - os: macos ruby: 2.4 From e04c9cdbd8ddcca239c4628380dee7dd36de873b Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 2 Jan 2022 13:57:09 +0000 Subject: [PATCH 362/469] Update compatibility matrix in the README. --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e5ddf6f2..f012e55d 100644 --- a/README.md +++ b/README.md @@ -360,12 +360,12 @@ You can set multiple settings at the same time by using a block: Rubyzip is known to run on a number of platforms and under a number of different Ruby versions. Please see the table below for what we think the current situation is. Note: an empty cell means "unknown", not "does not work". -| OS | 2.4 | 2.5 | 2.6 | 2.7 | 3.0 | Head | JRuby 9.2.17.0 | JRuby Head | Truffleruby 21.1.0 | Truffleruby Head | -|----|-----|-----|-----|-----|-----|------|----------------|------------|--------------------|------------------| -|Ubuntu 20.04| CI | CI | CI | CI | CI | ci | CI | ci | CI | ci | -|Mac OS 10.15.7| CI | x | x | x | x | | x | | x | | -|Windows 10| | | | x | | | | | | | -|Windows Server 2019| CI | | | | | | | | | | +| OS | 2.4 | 2.5 | 2.6 | 2.7 | 3.0 | 3.1 | Head | JRuby 9.2.17.0 | JRuby Head | Truffleruby 21.1.0 | Truffleruby Head | +|----|-----|-----|-----|-----|-----|-----|------|----------------|------------|--------------------|------------------| +|Ubuntu 20.04| CI | CI | CI | CI | CI | CI | ci | CI | ci | CI | ci | +|Mac OS 10.15.7| CI | x | x | x | x | x | | x | | x | | +|Windows 10| | | | x | | | | | | | | +|Windows Server 2019| CI | | | | | | | | | | | Key: `CI` - tested in CI, should work; `ci` - tested in CI, might fail; `x` - known working; `o` - known failing. From 099d379c82a3a6144e94a17021d8b39aed0977bc Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 10 Jan 2022 17:35:49 +0000 Subject: [PATCH 363/469] Add an extra test for YJIT in ruby 3.1. I tried adding this to the matrix, but I couldn't work out how to do this *and* keep a vanilla 3.1 test in the mix as well. It seems you can't add different `env`s with `include`, but maybe I missed something. --- .github/workflows/tests.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 338de224..b613695c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -44,6 +44,25 @@ jobs: flag-name: ${{ matrix.ruby }} parallel: true + test-yjit: + runs-on: ubuntu-latest + continue-on-error: true + steps: + - name: Checkout rubyzip code + uses: actions/checkout@v2 + + - name: Install and set up ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.1' + bundler-cache: true + + - name: Run the tests + env: + RUBYOPT: --enable-yjit -v + FULL_ZIP64_TEST: 1 + run: bundle exec rake + finish: needs: test runs-on: ubuntu-latest From 9bed9d053972f8afde49600dbc1921da8ebe38ea Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 11 Jan 2022 13:02:00 +0000 Subject: [PATCH 364/469] Expand the YJIT tests into a mini matrix. Test 3.1 and head on Ubuntu and MacOS. --- .github/workflows/tests.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b613695c..b4180a14 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -45,7 +45,12 @@ jobs: parallel: true test-yjit: - runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + os: [ubuntu, macos] + ruby: ['3.1', head] + runs-on: ${{ matrix.os }}-latest continue-on-error: true steps: - name: Checkout rubyzip code @@ -54,7 +59,7 @@ jobs: - name: Install and set up ruby uses: ruby/setup-ruby@v1 with: - ruby-version: '3.1' + ruby-version: ${{ matrix.ruby }} bundler-cache: true - name: Run the tests From 3fbc5e62f9f9d2274d85526cb9794370d695af52 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 11 Jan 2022 13:15:48 +0000 Subject: [PATCH 365/469] Add the YJIT tests to the README matrix. --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f012e55d..12165d5e 100644 --- a/README.md +++ b/README.md @@ -360,12 +360,12 @@ You can set multiple settings at the same time by using a block: Rubyzip is known to run on a number of platforms and under a number of different Ruby versions. Please see the table below for what we think the current situation is. Note: an empty cell means "unknown", not "does not work". -| OS | 2.4 | 2.5 | 2.6 | 2.7 | 3.0 | 3.1 | Head | JRuby 9.2.17.0 | JRuby Head | Truffleruby 21.1.0 | Truffleruby Head | -|----|-----|-----|-----|-----|-----|-----|------|----------------|------------|--------------------|------------------| -|Ubuntu 20.04| CI | CI | CI | CI | CI | CI | ci | CI | ci | CI | ci | -|Mac OS 10.15.7| CI | x | x | x | x | x | | x | | x | | -|Windows 10| | | | x | | | | | | | | -|Windows Server 2019| CI | | | | | | | | | | | +| OS | 2.4 | 2.5 | 2.6 | 2.7 | 3.0 | 3.1 | 3.1 +YJIT | Head | Head +YJIT | JRuby 9.2.17.0 | JRuby Head | Truffleruby 21.1.0 | Truffleruby Head | +|----|-----|-----|-----|-----|-----|-----|----------|------|-----------|----------------|------------|--------------------|------------------| +|Ubuntu 20.04| CI | CI | CI | CI | CI | CI | ci | ci | ci | CI | ci | CI | ci | +|Mac OS 10.15.7| CI | x | x | x | x | x | ci | | ci | x | | x | | +|Windows 10| | | | x | | | | | | | | | | +|Windows Server 2019| CI | | | | | | | | | | | | | Key: `CI` - tested in CI, should work; `ci` - tested in CI, might fail; `x` - known working; `o` - known failing. From 20952ef38f42b14b0324e3d24665c4fabb76f7f9 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 11 Jan 2022 13:19:31 +0000 Subject: [PATCH 366/469] Update version numbers in the README test matrix. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 12165d5e..9e93cb42 100644 --- a/README.md +++ b/README.md @@ -360,10 +360,10 @@ You can set multiple settings at the same time by using a block: Rubyzip is known to run on a number of platforms and under a number of different Ruby versions. Please see the table below for what we think the current situation is. Note: an empty cell means "unknown", not "does not work". -| OS | 2.4 | 2.5 | 2.6 | 2.7 | 3.0 | 3.1 | 3.1 +YJIT | Head | Head +YJIT | JRuby 9.2.17.0 | JRuby Head | Truffleruby 21.1.0 | Truffleruby Head | +| OS | 2.4 | 2.5 | 2.6 | 2.7 | 3.0 | 3.1 | 3.1 +YJIT | Head | Head +YJIT | JRuby 9.3.2.0 | JRuby Head | Truffleruby 21.3.0 | Truffleruby Head | |----|-----|-----|-----|-----|-----|-----|----------|------|-----------|----------------|------------|--------------------|------------------| -|Ubuntu 20.04| CI | CI | CI | CI | CI | CI | ci | ci | ci | CI | ci | CI | ci | -|Mac OS 10.15.7| CI | x | x | x | x | x | ci | | ci | x | | x | | +|Ubuntu 20.04.3| CI | CI | CI | CI | CI | CI | ci | ci | ci | CI | ci | CI | ci | +|Mac OS 11.6.2| CI | x | x | x | x | x | ci | | ci | x | | x | | |Windows 10| | | | x | | | | | | | | | | |Windows Server 2019| CI | | | | | | | | | | | | | From 90728d71095cb03b1b4b2e2d5eb4ce0edd223d2d Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 11 Jan 2022 13:24:35 +0000 Subject: [PATCH 367/469] Add `-v` switch to ruby for all tests. --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b4180a14..d9e6147d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -32,6 +32,7 @@ jobs: - name: Run the tests env: + RUBYOPT: -v JRUBY_OPTS: --debug FULL_ZIP64_TEST: 1 run: bundle exec rake From cf258bbb719f1c8dca4ad73a2eabb130f4c88864 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 11 Jan 2022 22:01:49 +0000 Subject: [PATCH 368/469] Move to ruby 2.5 as the earliest supported version. 2.4 is nearly two years beyond EOL now. Closes #484. --- .github/workflows/lint.yml | 2 +- .github/workflows/tests.yml | 6 +++--- .rubocop.yml | 2 +- .simplecov | 2 +- lib/zip/file.rb | 12 +++++------- rubyzip.gemspec | 2 +- test/filesystem/file_nonmutating_test.rb | 2 +- 7 files changed, 13 insertions(+), 15 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index dccec7f3..66c262fb 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,7 +12,7 @@ jobs: - name: Install and set up ruby uses: ruby/setup-ruby@v1 with: - ruby-version: 2.4 + ruby-version: '2.5' bundler-cache: true - name: Rubocop diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d9e6147d..49fa1a55 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,12 +8,12 @@ jobs: fail-fast: false matrix: os: [ubuntu] - ruby: ['2.4', '2.5', '2.6', '2.7', '3.0', '3.1', head, jruby, jruby-head, truffleruby, truffleruby-head] + ruby: ['2.5', '2.6', '2.7', '3.0', '3.1', head, jruby, jruby-head, truffleruby, truffleruby-head] include: - os: macos - ruby: 2.4 + ruby: '2.5' - os: windows - ruby: 2.4 + ruby: '2.5' runs-on: ${{ matrix.os }}-latest continue-on-error: ${{ endsWith(matrix.ruby, 'head') || matrix.os == 'windows' }} steps: diff --git a/.rubocop.yml b/.rubocop.yml index c6d2a51d..1997fe39 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -7,7 +7,7 @@ inherit_from: .rubocop_todo.yml # Set this to the minimum supported ruby in the gemspec. Otherwise # we get errors if our ruby version doesn't match. AllCops: - TargetRubyVersion: 2.4 + TargetRubyVersion: 2.5 NewCops: enable # Allow this in this file because adding the extra lines is pointless. diff --git a/.simplecov b/.simplecov index 611a9527..91415dfd 100644 --- a/.simplecov +++ b/.simplecov @@ -17,6 +17,6 @@ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new( ) SimpleCov.start do - # enable_coverage :branch <-- Re-enable this when we move to ruby ~> 2.5. + enable_coverage :branch add_filter ['/test/', '/samples/'] end diff --git a/lib/zip/file.rb b/lib/zip/file.rb index ddb5e736..661609b1 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -406,14 +406,12 @@ def check_file(path) def on_success_replace dirname, basename = ::File.split(name) ::Dir::Tmpname.create(basename, dirname) do |tmp_filename| - begin - if yield tmp_filename - ::File.rename(tmp_filename, name) - ::File.chmod(@file_permissions, name) unless @create - end - ensure - ::File.unlink(tmp_filename) if ::File.exist?(tmp_filename) + if yield tmp_filename + ::File.rename(tmp_filename, name) + ::File.chmod(@file_permissions, name) unless @create end + ensure + ::File.unlink(tmp_filename) if ::File.exist?(tmp_filename) end end end diff --git a/rubyzip.gemspec b/rubyzip.gemspec index 1a913c03..5287d0cd 100644 --- a/rubyzip.gemspec +++ b/rubyzip.gemspec @@ -25,7 +25,7 @@ Gem::Specification.new do |s| 'wiki_uri' => 'https://github.com/rubyzip/rubyzip/wiki' } - s.required_ruby_version = '>= 2.4' + s.required_ruby_version = '>= 2.5' s.add_development_dependency 'minitest', '~> 5.4' s.add_development_dependency 'rake', '~> 12.3.3' diff --git a/test/filesystem/file_nonmutating_test.rb b/test/filesystem/file_nonmutating_test.rb index d4007e6f..0e93b641 100644 --- a/test/filesystem/file_nonmutating_test.rb +++ b/test/filesystem/file_nonmutating_test.rb @@ -445,7 +445,7 @@ def test_glob '*/foo/**/*.txt' => ['globTest/foo/bar/baz/foo.txt'] }.each do |spec, expected_results| results = zf.glob(spec) - assert(results.all? { |entry| entry.kind_of? ::Zip::Entry }) + assert(results.all?(::Zip::Entry)) result_strings = results.map(&:to_s) missing_matches = expected_results - result_strings From 7f7c4ca194ab96caf966163b6d14f6e67eb1d386 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 11 Jan 2022 22:18:18 +0000 Subject: [PATCH 369/469] Update README after the move to Ruby >= 2.5. --- README.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 9e93cb42..4e9d8885 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,11 @@ gem 'zip-zip' # will load compatibility for old rubyzip API. ## Requirements -- Ruby 2.4 or greater (for rubyzip 2.0; use 1.x for older rubies) +Version 3.x requires at least Ruby 2.5. + +Version 2.x requires at least Ruby 2.4, and is know to work on Ruby 3.1. + +It is not recommended to use any versions of Rubyzip earlier than 2.3 due to security issues. ## Installation @@ -360,12 +364,12 @@ You can set multiple settings at the same time by using a block: Rubyzip is known to run on a number of platforms and under a number of different Ruby versions. Please see the table below for what we think the current situation is. Note: an empty cell means "unknown", not "does not work". -| OS | 2.4 | 2.5 | 2.6 | 2.7 | 3.0 | 3.1 | 3.1 +YJIT | Head | Head +YJIT | JRuby 9.3.2.0 | JRuby Head | Truffleruby 21.3.0 | Truffleruby Head | -|----|-----|-----|-----|-----|-----|-----|----------|------|-----------|----------------|------------|--------------------|------------------| -|Ubuntu 20.04.3| CI | CI | CI | CI | CI | CI | ci | ci | ci | CI | ci | CI | ci | -|Mac OS 11.6.2| CI | x | x | x | x | x | ci | | ci | x | | x | | -|Windows 10| | | | x | | | | | | | | | | -|Windows Server 2019| CI | | | | | | | | | | | | | +| OS/Ruby | 2.5 | 2.6 | 2.7 | 3.0 | 3.1 | 3.1 +YJIT | Head | Head +YJIT | JRuby 9.3.2.0 | JRuby Head | Truffleruby 21.3.0 | Truffleruby Head | +|---------|-----|-----|-----|-----|-----|----------|------|-----------|----------------|------------|--------------------|------------------| +|Ubuntu 20.04.3| CI | CI | CI | CI | CI | ci | ci | ci | CI | ci | CI | ci | +|Mac OS 11.6.2| CI | x | x | x | x | ci | | ci | x | | x | | +|Windows 10| | | x | | | | | | | | | | +|Windows Server 2019| CI | | | | | | | | | | | | Key: `CI` - tested in CI, should work; `ci` - tested in CI, might fail; `x` - known working; `o` - known failing. From b1b82bbd9eb0fbd651744dd0916f650c4420634b Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 11 Jan 2022 22:21:20 +0000 Subject: [PATCH 370/469] Tidy up updating notes in README. --- README.md | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/README.md b/README.md index 4e9d8885..d2c98da9 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Rubyzip is a ruby library for reading and writing zip files. ## Important notes -### Version 3.0 +### Updating to version 3.0 The public API of some classes has been modernized to use named parameters for optional arguments. Please check your usage of the following Rubyzip classes: * `File` @@ -18,17 +18,6 @@ The public API of some classes has been modernized to use named parameters for o * `InputStream` * `OutputStream` -### Older versions (pre 2.0) - -The Rubyzip interface has changed!!! No need to do `require "zip/zip"` and `Zip` prefix in class names removed. - -If you have issues with any third-party gems that require an old version of rubyzip, you can use this workaround: - -```ruby -gem 'rubyzip', '>= 1.0.0' # will load new rubyzip version -gem 'zip-zip' # will load compatibility for old rubyzip API. -``` - ## Requirements Version 3.x requires at least Ruby 2.5. From f8b9d07022e23b2aca08b10fce4b90f92987a99f Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 11 Jan 2022 20:16:53 +0000 Subject: [PATCH 371/469] Round out EOCD data size constants in CDir. --- lib/zip/central_directory.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/zip/central_directory.rb b/lib/zip/central_directory.rb index e45fc030..676f863b 100644 --- a/lib/zip/central_directory.rb +++ b/lib/zip/central_directory.rb @@ -7,9 +7,12 @@ class CentralDirectory END_OF_CDS = 0x06054b50 ZIP64_END_OF_CDS = 0x06064b50 ZIP64_EOCD_LOCATOR = 0x07064b50 + STATIC_EOCD_SIZE = 22 ZIP64_STATIC_EOCD_SIZE = 56 - MAX_END_OF_CDS_SIZE = 65_535 + STATIC_EOCD_SIZE + ZIP64_EOCD_LOC_SIZE = 20 + MAX_FILE_COMMENT_SIZE = 1 << 16 + MAX_END_OF_CDS_SIZE = MAX_FILE_COMMENT_SIZE + STATIC_EOCD_SIZE attr_reader :comment From 34731b18850b553f8aa42cb891941394a7cb0b77 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 15 Jan 2022 13:10:54 +0000 Subject: [PATCH 372/469] `Zip::File` no longer subclasses `Zip::CentralDirectory`. It has bothered me for years that the central directory is exposed in this way. A zip file should *have* a central directory, but it should not *be* one. This commit starts us down the path of properly separating the two. --- lib/zip/central_directory.rb | 6 ++-- lib/zip/file.rb | 60 ++++++++++++++++++------------------ 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/lib/zip/central_directory.rb b/lib/zip/central_directory.rb index 676f863b..3d3cd1a3 100644 --- a/lib/zip/central_directory.rb +++ b/lib/zip/central_directory.rb @@ -2,8 +2,6 @@ module Zip class CentralDirectory - include Enumerable - END_OF_CDS = 0x06054b50 ZIP64_END_OF_CDS = 0x06064b50 ZIP64_EOCD_LOCATOR = 0x07064b50 @@ -14,7 +12,9 @@ class CentralDirectory MAX_FILE_COMMENT_SIZE = 1 << 16 MAX_END_OF_CDS_SIZE = MAX_FILE_COMMENT_SIZE + STATIC_EOCD_SIZE - attr_reader :comment + attr_accessor :comment + + attr_reader :entry_set # Returns an Enumerable containing the entries. def entries diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 661609b1..109cd86a 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'forwardable' + require_relative 'file_split' module Zip @@ -45,8 +47,8 @@ module Zip # # ZipFileSystem offers an alternative API that emulates ruby's # interface for accessing the filesystem, ie. the File and Dir classes. - - class File < CentralDirectory + class File + extend Forwardable extend FileSplit IO_METHODS = [:tell, :seek, :read, :eof, :close].freeze @@ -62,8 +64,7 @@ class File < CentralDirectory # default -> true. attr_accessor :restore_times - # Returns the zip files comment, if it has one - attr_accessor :comment + def_delegators :@cdir, :comment, :comment=, :each, :entries, :size # Opens a zip archive. Pass create: true to create # a new archive if it doesn't exist already. @@ -73,8 +74,8 @@ def initialize(path_or_io, create: false, buffer: false, **options) .merge(compression_level: ::Zip.default_compression) .merge(options) @name = path_or_io.respond_to?(:path) ? path_or_io.path : path_or_io - @comment = '' @create = create ? true : false # allow any truthy value to mean true + @cdir = ::Zip::CentralDirectory.new if ::File.size?(@name.to_s) # There is a file, which exists, that is associated with this zip. @@ -82,29 +83,28 @@ def initialize(path_or_io, create: false, buffer: false, **options) @file_permissions = ::File.stat(@name).mode if buffer - read_from_stream(path_or_io) + @cdir.read_from_stream(path_or_io) else ::File.open(@name, 'rb') do |f| - read_from_stream(f) + @cdir.read_from_stream(f) end end elsif buffer && path_or_io.size > 0 # This zip is probably a non-empty StringIO. @create = false - read_from_stream(path_or_io) - elsif @create - # This zip is completely new/empty and is to be created. - @entry_set = EntrySet.new - elsif ::File.zero?(@name) - # A file exists, but it is empty. + @cdir.read_from_stream(path_or_io) + elsif !@create && ::File.zero?(@name) + # A file exists, but it is empty, and we've said we're + # NOT creating a new zip. raise Error, "File #{@name} has zero size. Did you mean to pass the create flag?" - else - # Everything is wrong. + elsif !@create + # If we get here, and we're not creating a new zip, then + # everything is wrong. raise Error, "File #{@name} not found" end - @stored_entries = @entry_set.dup - @stored_comment = @comment + @stored_entries = @cdir.entry_set.dup + @stored_comment = @cdir.comment @restore_ownership = options[:restore_ownership] @restore_permissions = options[:restore_permissions] @restore_times = options[:restore_times] @@ -222,7 +222,7 @@ def get_output_stream(entry, permissions: nil, comment: nil, end new_entry.unix_perms = permissions zip_streamable_entry = StreamableStream.new(new_entry) - @entry_set << zip_streamable_entry + @cdir.entry_set << zip_streamable_entry zip_streamable_entry.get_output_stream(&a_proc) end @@ -250,7 +250,7 @@ def add(entry, src_path, &continue_on_exists_proc) end new_entry.gather_fileinfo_from_srcpath(src_path) new_entry.dirty = true - @entry_set << new_entry + @cdir.entry_set << new_entry end # Convenience method for adding the contents of a file to the archive @@ -264,16 +264,16 @@ def add_stored(entry, src_path, &continue_on_exists_proc) # Removes the specified entry. def remove(entry) - @entry_set.delete(get_entry(entry)) + @cdir.entry_set.delete(get_entry(entry)) end # Renames the specified entry. def rename(entry, new_name, &continue_on_exists_proc) found_entry = get_entry(entry) check_entry_exists(new_name, continue_on_exists_proc, 'rename') - @entry_set.delete(found_entry) + @cdir.entry_set.delete(found_entry) found_entry.name = new_name - @entry_set << found_entry + @cdir.entry_set << found_entry end # Replaces the specified entry with the contents of src_path (from @@ -298,7 +298,7 @@ def commit on_success_replace do |tmp_file| ::Zip::OutputStream.open(tmp_file) do |zos| - @entry_set.each do |e| + @cdir.each do |e| e.write_to_zip_output_stream(zos) e.dirty = false e.clean_up @@ -315,7 +315,7 @@ def write_buffer(io = ::StringIO.new) return unless commit_required? ::Zip::OutputStream.write_buffer(io) do |zos| - @entry_set.each { |e| e.write_to_zip_output_stream(zos) } + @cdir.each { |e| e.write_to_zip_output_stream(zos) } zos.comment = comment end end @@ -328,16 +328,16 @@ def close # Returns true if any changes has been made to this archive since # the previous commit def commit_required? - @entry_set.each do |e| + @cdir.each do |e| return true if e.dirty end - @comment != @stored_comment || @entry_set != @stored_entries || @create + comment != @stored_comment || @cdir.entry_set != @stored_entries || @create end # Searches for entry with the specified name. Returns nil if # no entry is found. See also get_entry def find_entry(entry_name) - selected_entry = @entry_set.find_entry(entry_name) + selected_entry = @cdir.entry_set.find_entry(entry_name) return if selected_entry.nil? selected_entry.restore_ownership = @restore_ownership @@ -352,7 +352,7 @@ def find_entry(entry_name) # `::File::FNM_PATHNAME | ::File::FNM_DOTMATCH | ::File::FNM_EXTGLOB`, # which will be overridden if you set your own flags. def glob(*args, &block) - @entry_set.glob(*args, &block) + @cdir.entry_set.glob(*args, &block) end # Searches for an entry just as find_entry, but throws Errno::ENOENT @@ -370,7 +370,7 @@ def mkdir(entry_name, permission = 0o755) entry_name = entry_name.dup.to_s entry_name << '/' unless entry_name.end_with?('/') - @entry_set << ::Zip::StreamableDirectory.new(@name, entry_name, nil, permission) + @cdir.entry_set << ::Zip::StreamableDirectory.new(@name, entry_name, nil, permission) end private @@ -389,7 +389,7 @@ def directory?(new_entry, src_path) def check_entry_exists(entry_name, continue_on_exists_proc, proc_name) continue_on_exists_proc ||= proc { Zip.continue_on_exists_proc } - return unless @entry_set.include?(entry_name) + return unless @cdir.entry_set.include?(entry_name) if continue_on_exists_proc.call remove get_entry(entry_name) From 1d6bfb7e6912e7725324843edc83327e0f9df82c Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 15 Jan 2022 22:11:23 +0000 Subject: [PATCH 373/469] Expose the `EntrySet` more cleanly through `CentralDirectory`. There is now no direct access to the set of entries in a central directory. This makes the interface cleaner because we now, for example, add/delete things directly to/from the central directory, rather than to/from the entry set contained within the central directory. --- lib/zip/central_directory.rb | 24 +++++++----------------- lib/zip/file.rb | 31 +++++++++++-------------------- 2 files changed, 18 insertions(+), 37 deletions(-) diff --git a/lib/zip/central_directory.rb b/lib/zip/central_directory.rb index 3d3cd1a3..4ea444d4 100644 --- a/lib/zip/central_directory.rb +++ b/lib/zip/central_directory.rb @@ -1,7 +1,11 @@ # frozen_string_literal: true +require 'forwardable' + module Zip class CentralDirectory + extend Forwardable + END_OF_CDS = 0x06054b50 ZIP64_END_OF_CDS = 0x06064b50 ZIP64_EOCD_LOCATOR = 0x07064b50 @@ -14,12 +18,9 @@ class CentralDirectory attr_accessor :comment - attr_reader :entry_set - - # Returns an Enumerable containing the entries. - def entries - @entry_set.entries - end + def_delegators :@entry_set, + :<<, :delete, :each, :entries, :find_entry, :glob, + :include?, :size def initialize(entries = EntrySet.new, comment = '') #:nodoc: super() @@ -220,17 +221,6 @@ def start_buf(io) io.read end - # For iterating over the entries. - def each(&a_proc) - @entry_set.each(&a_proc) - end - - # Returns the number of entries in the central directory (and - # consequently in the zip archive). - def size - @entry_set.size - end - # Reads the End of Central Directory Record (and the Zip64 equivalent if # needs be) and returns the number of entries in the archive. This is a # convenience method that avoids reading in all of the entry data to get a diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 109cd86a..1aa34575 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -64,7 +64,7 @@ class File # default -> true. attr_accessor :restore_times - def_delegators :@cdir, :comment, :comment=, :each, :entries, :size + def_delegators :@cdir, :comment, :comment=, :each, :entries, :glob, :size # Opens a zip archive. Pass create: true to create # a new archive if it doesn't exist already. @@ -103,7 +103,7 @@ def initialize(path_or_io, create: false, buffer: false, **options) raise Error, "File #{@name} not found" end - @stored_entries = @cdir.entry_set.dup + @stored_entries = @cdir.entries.map(&:dup) @stored_comment = @cdir.comment @restore_ownership = options[:restore_ownership] @restore_permissions = options[:restore_permissions] @@ -222,7 +222,7 @@ def get_output_stream(entry, permissions: nil, comment: nil, end new_entry.unix_perms = permissions zip_streamable_entry = StreamableStream.new(new_entry) - @cdir.entry_set << zip_streamable_entry + @cdir << zip_streamable_entry zip_streamable_entry.get_output_stream(&a_proc) end @@ -250,7 +250,7 @@ def add(entry, src_path, &continue_on_exists_proc) end new_entry.gather_fileinfo_from_srcpath(src_path) new_entry.dirty = true - @cdir.entry_set << new_entry + @cdir << new_entry end # Convenience method for adding the contents of a file to the archive @@ -264,16 +264,16 @@ def add_stored(entry, src_path, &continue_on_exists_proc) # Removes the specified entry. def remove(entry) - @cdir.entry_set.delete(get_entry(entry)) + @cdir.delete(get_entry(entry)) end # Renames the specified entry. def rename(entry, new_name, &continue_on_exists_proc) found_entry = get_entry(entry) check_entry_exists(new_name, continue_on_exists_proc, 'rename') - @cdir.entry_set.delete(found_entry) + @cdir.delete(found_entry) found_entry.name = new_name - @cdir.entry_set << found_entry + @cdir << found_entry end # Replaces the specified entry with the contents of src_path (from @@ -331,13 +331,13 @@ def commit_required? @cdir.each do |e| return true if e.dirty end - comment != @stored_comment || @cdir.entry_set != @stored_entries || @create + comment != @stored_comment || entries != @stored_entries || @create end # Searches for entry with the specified name. Returns nil if # no entry is found. See also get_entry def find_entry(entry_name) - selected_entry = @cdir.entry_set.find_entry(entry_name) + selected_entry = @cdir.find_entry(entry_name) return if selected_entry.nil? selected_entry.restore_ownership = @restore_ownership @@ -346,15 +346,6 @@ def find_entry(entry_name) selected_entry end - # Search for entries given a glob pattern. You can also supply flags - # in the second argument, which are equivalent to those used by - # `::Dir.glob` and `::File.fnmatch`. Default flags are - # `::File::FNM_PATHNAME | ::File::FNM_DOTMATCH | ::File::FNM_EXTGLOB`, - # which will be overridden if you set your own flags. - def glob(*args, &block) - @cdir.entry_set.glob(*args, &block) - end - # Searches for an entry just as find_entry, but throws Errno::ENOENT # if no entry is found. def get_entry(entry) @@ -370,7 +361,7 @@ def mkdir(entry_name, permission = 0o755) entry_name = entry_name.dup.to_s entry_name << '/' unless entry_name.end_with?('/') - @cdir.entry_set << ::Zip::StreamableDirectory.new(@name, entry_name, nil, permission) + @cdir << ::Zip::StreamableDirectory.new(@name, entry_name, nil, permission) end private @@ -389,7 +380,7 @@ def directory?(new_entry, src_path) def check_entry_exists(entry_name, continue_on_exists_proc, proc_name) continue_on_exists_proc ||= proc { Zip.continue_on_exists_proc } - return unless @cdir.entry_set.include?(entry_name) + return unless @cdir.include?(entry_name) if continue_on_exists_proc.call remove get_entry(entry_name) From 9c3f8254c7f7f992c94cdea4d732ef0f9e5f68c3 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 17 Jan 2022 17:45:19 +0000 Subject: [PATCH 374/469] Fix reading zip64 files with max length file comment. If a zip file has a comment that is 65,535 characters long - which is a valid length and the maximum allowable length - the initial read of the archive fails to find the Zip64 End of Central Directory Locator and therefore cannot read the rest of the file. This commit fixes this by making sure that we look far enough back into the file from the end to find this locator, and then use the information in it to find the Zip64 End of Central Directory Record. Test added to catch regressions. Fixes #509. --- lib/zip/central_directory.rb | 86 ++++++++++++-------- test/data/zip64_max_length_file_comment.zip | Bin 0 -> 66351 bytes test/file_test.rb | 5 ++ 3 files changed, 57 insertions(+), 34 deletions(-) create mode 100644 test/data/zip64_max_length_file_comment.zip diff --git a/lib/zip/central_directory.rb b/lib/zip/central_directory.rb index 4ea444d4..f5dd25a5 100644 --- a/lib/zip/central_directory.rb +++ b/lib/zip/central_directory.rb @@ -6,15 +6,16 @@ module Zip class CentralDirectory extend Forwardable - END_OF_CDS = 0x06054b50 - ZIP64_END_OF_CDS = 0x06064b50 - ZIP64_EOCD_LOCATOR = 0x07064b50 + END_OF_CD_SIG = 0x06054b50 + ZIP64_END_OF_CD_SIG = 0x06064b50 + ZIP64_EOCD_LOCATOR_SIG = 0x07064b50 STATIC_EOCD_SIZE = 22 ZIP64_STATIC_EOCD_SIZE = 56 ZIP64_EOCD_LOC_SIZE = 20 - MAX_FILE_COMMENT_SIZE = 1 << 16 - MAX_END_OF_CDS_SIZE = MAX_FILE_COMMENT_SIZE + STATIC_EOCD_SIZE + MAX_FILE_COMMENT_SIZE = (1 << 16) - 1 + MAX_END_OF_CD_SIZE = + MAX_FILE_COMMENT_SIZE + STATIC_EOCD_SIZE + ZIP64_EOCD_LOC_SIZE attr_accessor :comment @@ -47,7 +48,7 @@ def write_to_stream(io) #:nodoc: def write_e_o_c_d(io, offset, cdir_size) #:nodoc: tmp = [ - END_OF_CDS, + END_OF_CD_SIG, 0, # @numberOfThisDisk 0, # @numberOfDiskWithStartOfCDir @entry_set ? [@entry_set.size, 0xFFFF].min : 0, @@ -64,7 +65,7 @@ def write_e_o_c_d(io, offset, cdir_size) #:nodoc: def write_64_e_o_c_d(io, offset, cdir_size) #:nodoc: tmp = [ - ZIP64_END_OF_CDS, + ZIP64_END_OF_CD_SIG, 44, # size of zip64 end of central directory record (excludes signature and field itself) VERSION_MADE_BY, VERSION_NEEDED_TO_EXTRACT_ZIP64, @@ -82,7 +83,7 @@ def write_64_e_o_c_d(io, offset, cdir_size) #:nodoc: def write_64_eocd_locator(io, zip64_eocd_offset) tmp = [ - ZIP64_EOCD_LOCATOR, + ZIP64_EOCD_LOCATOR_SIG, 0, # number of disk containing the start of zip64 eocd record zip64_eocd_offset, # offset of the start of zip64 eocd record in its disk 1 # total number of disks @@ -93,15 +94,7 @@ def write_64_eocd_locator(io, zip64_eocd_offset) private :write_64_eocd_locator def unpack_64_e_o_c_d(buffer) #:nodoc: - index = buffer.rindex([ZIP64_END_OF_CDS].pack('V')) - raise Error, 'Zip64 end of central directory signature not found' unless index - - l_index = buffer.rindex([ZIP64_EOCD_LOCATOR].pack('V')) - raise Error, 'Zip64 end of central directory signature locator not found' unless l_index - - buf = buffer.slice(index..l_index) - - _, # ZIP64_END_OF_CDS signature. We know we have this at this point. + _, # ZIP64_END_OF_CD_SIG. We know we have this at this point. @size_of_zip64_e_o_c_d, @version_made_by, @version_needed_for_extract, @@ -110,7 +103,7 @@ def unpack_64_e_o_c_d(buffer) #:nodoc: @total_number_of_entries_in_cdir_on_this_disk, @size, @size_in_bytes, - @cdir_offset = buf.unpack('VQG4NvJreqwDDahC`4jpZV^!uX^q^8WfvOw8|(ySVa** ziTE0$MyAGXyc!)Y8eOzDI@|yWGT%Y5wwlcue-OtOwRbHOdX6H2SO{x3G3oq>x4B6CDC6zkbJv*;r0*0JnsV$JZG3iuFh{y zHs`6t3n^Swmt9?YDY{Q$*Q<~nk)!9AYR|-4)4lm?@2Y-J{c!Kg&!>48UAbIVZZTHK zW5Xkz=Vp0~VeKWwx}vnNJkp9!>hez4j6U$)`8bEl9y49nedx=ve;*iO9ADVayWjYY zy`ZBzs-e8~ioNsUQSa^~iy`j94-uz|TrW3C1*MjRubbUdoFfJ{X@5QFG(DYMdu-_X z{+THwn{(>hR!tq5<1Bo`#v zNuJwru{U>m+iH?8KCS9=xbvewG{?=>l=;~)O;Y9BqI*LApKR3kgJ+aDx9;vC3-gh! zTeW6Uh9zhD;*4H+{{+x}?2UIj{$8>I*n?-=b~fZD`pni?d=jLqh)=%E`S(30_4>?vlNHy_`WeJat z|66z0kb7hOX{p$iE=M@@4bSrCUiapEPE&ZgkMnJX9#F+eF$?Qe;ZMgT zU(NS;|Er}?n)8%e8cn3~B47Bw%0CbC529z>9$3c*625a Date: Mon, 17 Jan 2022 18:03:04 +0000 Subject: [PATCH 375/469] Round out the max comment size tests. Just sanity check the comment size and the number of entries once the file has been initialized. --- test/file_test.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/file_test.rb b/test/file_test.rb index 8715c4ed..b93c4e0d 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -205,12 +205,18 @@ def test_open_buffer_without_block_write_buffer_does_nothing def test_open_file_with_max_length_comment # Should not raise any errors. - Zip::File.open('test/data/max_length_file_comment.zip') + Zip::File.open('test/data/max_length_file_comment.zip') do |zf| + assert_equal(1, zf.size) + assert_equal(65_535, zf.comment.length) + end end def test_open_zip64_file_with_max_length_comment # Should not raise any errors. - Zip::File.open('test/data/zip64_max_length_file_comment.zip') + Zip::File.open('test/data/zip64_max_length_file_comment.zip') do |zf| + assert_equal(2, zf.size) + assert_equal(65_535, zf.comment.length) + end end def test_count_entries From bdbd573290be5d7ff103fccb3d51291f84b63167 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 17 Jan 2022 18:10:17 +0000 Subject: [PATCH 376/469] Remove unnecessary static method from `CentralDirectory`. `CentralDirectory` shouldn't be in the public API for rubyzip and there's nothing that `CentralDirectory::read_from_stream` did that couldn't be done by just initializing an object first. Keeping it around risked things getting out of date as we streamline and fix other things. --- lib/zip/central_directory.rb | 8 -------- test/central_directory_test.rb | 3 ++- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/lib/zip/central_directory.rb b/lib/zip/central_directory.rb index f5dd25a5..55953315 100644 --- a/lib/zip/central_directory.rb +++ b/lib/zip/central_directory.rb @@ -248,14 +248,6 @@ def count_entries(io) @size end - def self.read_from_stream(io) #:nodoc: - cdir = new - cdir.read_from_stream(io) - cdir - rescue Error - nil - end - def ==(other) #:nodoc: return false unless other.kind_of?(CentralDirectory) diff --git a/test/central_directory_test.rb b/test/central_directory_test.rb index 88258caa..6197eb3b 100644 --- a/test/central_directory_test.rb +++ b/test/central_directory_test.rb @@ -9,7 +9,8 @@ def teardown def test_read_from_stream ::File.open(TestZipFile::TEST_ZIP2.zip_name, 'rb') do |zip_file| - cdir = ::Zip::CentralDirectory.read_from_stream(zip_file) + cdir = ::Zip::CentralDirectory.new + cdir.read_from_stream(zip_file) assert_equal(TestZipFile::TEST_ZIP2.entry_names.size, cdir.size) assert_equal(cdir.entries.map(&:name).sort, TestZipFile::TEST_ZIP2.entry_names.sort) From 60f8fffbc21d2abf57e80f3e939bba3370716d85 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 17 Jan 2022 22:04:45 +0000 Subject: [PATCH 377/469] Reorder methods in `CentralDirectory` with private at the end. --- lib/zip/central_directory.rb | 48 +++++++++++++++++------------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/lib/zip/central_directory.rb b/lib/zip/central_directory.rb index 55953315..171d0243 100644 --- a/lib/zip/central_directory.rb +++ b/lib/zip/central_directory.rb @@ -29,6 +29,11 @@ def initialize(entries = EntrySet.new, comment = '') #:nodoc: @comment = comment end + def read_from_stream(io) + read_eocds(io) + read_central_directory_entries(io) + end + def write_to_stream(io) #:nodoc: cdir_offset = io.tell @entry_set.each { |entry| entry.write_c_dir_entry(io) } @@ -46,6 +51,23 @@ def write_to_stream(io) #:nodoc: write_e_o_c_d(io, cdir_offset, cdir_size) end + # Reads the End of Central Directory Record (and the Zip64 equivalent if + # needs be) and returns the number of entries in the archive. This is a + # convenience method that avoids reading in all of the entry data to get a + # very quick entry count. + def count_entries(io) + read_eocds(io) + @size + end + + def ==(other) #:nodoc: + return false unless other.kind_of?(CentralDirectory) + + @entry_set.entries.sort == other.entries.sort && comment == other.comment + end + + private + def write_e_o_c_d(io, offset, cdir_size) #:nodoc: tmp = [ END_OF_CD_SIG, @@ -61,8 +83,6 @@ def write_e_o_c_d(io, offset, cdir_size) #:nodoc: io << @comment end - private :write_e_o_c_d - def write_64_e_o_c_d(io, offset, cdir_size) #:nodoc: tmp = [ ZIP64_END_OF_CD_SIG, @@ -79,8 +99,6 @@ def write_64_e_o_c_d(io, offset, cdir_size) #:nodoc: io << tmp.pack('VQ Date: Mon, 17 Jan 2022 22:32:56 +0000 Subject: [PATCH 378/469] `OutputStream`: use a `CentralDirectory` object internally. Now `CentralDirectory` is a bit cleaner it actually makes sense to use it here instead of an `EntrySet` and comment separately. --- lib/zip/output_stream.rb | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/lib/zip/output_stream.rb b/lib/zip/output_stream.rb index f2e7eb5e..58d2772c 100644 --- a/lib/zip/output_stream.rb +++ b/lib/zip/output_stream.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'forwardable' + module Zip # ZipOutputStream is the basic class for writing zip files. It is # possible to create a ZipOutputStream object directly, passing @@ -20,9 +22,10 @@ module Zip # class. class OutputStream + extend Forwardable include ::Zip::IOExtras::AbstractOutputStream - attr_accessor :comment + def_delegators :@cdir, :comment, :comment= # Opens the indicated zip file. If a file with that name already # exists it will be overwritten. @@ -37,12 +40,11 @@ def initialize(file_name, stream: false, encrypter: nil) else ::File.new(@file_name, 'wb') end - @entry_set = ::Zip::EntrySet.new + @cdir = ::Zip::CentralDirectory.new @compressor = ::Zip::NullCompressor.instance @encrypter = encrypter || ::Zip::NullEncrypter.new @closed = false @current_entry = nil - @comment = nil end # Same as #initialize but if a block is passed the opened @@ -73,7 +75,7 @@ def close finalize_current_entry update_local_headers - write_central_directory + @cdir.write_to_stream(@output_stream) @output_stream.close @closed = true end @@ -84,7 +86,7 @@ def close_buffer finalize_current_entry update_local_headers - write_central_directory + @cdir.write_to_stream(@output_stream) @closed = true @output_stream.flush @output_stream @@ -118,7 +120,7 @@ def copy_raw_entry(entry) raise Error, 'entry is not a ZipEntry' unless entry.kind_of?(Entry) finalize_current_entry - @entry_set << entry + @cdir << entry src_pos = entry.local_header_offset entry.write_local_entry(@output_stream) @compressor = NullCompressor.instance @@ -154,7 +156,7 @@ def finalize_current_entry def init_next_entry(entry) finalize_current_entry - @entry_set << entry + @cdir << entry entry.write_local_entry(@output_stream) @encrypter.reset! @output_stream << @encrypter.header(entry.mtime) @@ -175,18 +177,13 @@ def get_compressor(entry) def update_local_headers pos = @output_stream.pos - @entry_set.each do |entry| + @cdir.each do |entry| @output_stream.pos = entry.local_header_offset entry.write_local_entry(@output_stream, rewrite: true) end @output_stream.pos = pos end - def write_central_directory - cdir = CentralDirectory.new(@entry_set, @comment) - cdir.write_to_stream(@output_stream) - end - protected def finish From 4cf801c5f334d989000aeabfed53edd1263551a0 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 18 Jan 2022 15:12:53 +0000 Subject: [PATCH 379/469] Tidy up `EntrySet` accessors. `entry_order` is no longer a member, so remove it. `entry_set` should not be public, but needs to be protected for use in `==`. --- lib/zip/entry_set.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/zip/entry_set.rb b/lib/zip/entry_set.rb index a771dc7f..11db9518 100644 --- a/lib/zip/entry_set.rb +++ b/lib/zip/entry_set.rb @@ -3,7 +3,9 @@ module Zip class EntrySet #:nodoc:all include Enumerable - attr_accessor :entry_set, :entry_order + + attr_reader :entry_set + protected :entry_set def initialize(an_enumerable = []) super() From 044759f50263f9263461fffc65ab2c364301c9ad Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 7 Nov 2021 22:15:18 +0000 Subject: [PATCH 380/469] Fix `OutputStream#put_next_entry` to preserve `StreamableStream`s. When passing an `Entry` type to `File#get_output_stream` the entry is used to create a `StreamableStream`, which preserves all the info in the entry, such as timestamp, etc. But then in `put_next_entry` all that is lost due to the test for `kind_of?(Entry)` which a `StreamableStream` is not. See #503 for details. This change tests for `StreamableStream`s in `put_next_entry` and uses them directly. Some set-up within `Entry` needed to be made more robust to cope with this, but otherwise it's a low impact change, which does fix the problem. The reason this case was being missed before is that the tests weren't testing `get_output_stream` with an `Entry` object, so I have also added that test too. Fixes #503. --- lib/zip/entry.rb | 23 +++++++++++++++-------- lib/zip/output_stream.rb | 18 +++++++++--------- test/entry_test.rb | 5 ++++- test/file_test.rb | 23 ++++++++++++++++++++++- 4 files changed, 50 insertions(+), 19 deletions(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index d1241e56..9764d91d 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -83,14 +83,21 @@ def initialize( @ftype = name_is_directory? ? :directory : :file @zipfile = zipfile - @comment = comment - @compression_method = compression_method - @compression_level = compression_level - - @compressed_size = compressed_size - @crc = crc - @size = size - @time = time + @comment = comment || '' + @compression_method = compression_method || DEFLATED + @compression_level = compression_level || ::Zip.default_compression + + @compressed_size = compressed_size || 0 + @crc = crc || 0 + @size = size || 0 + @time = case time + when ::Zip::DOSTime + time + when Time + ::Zip::DOSTime.from_time(time) + else + ::Zip::DOSTime.now + end @extra = extra.kind_of?(ExtraField) ? extra : ExtraField.new(extra.to_s) diff --git a/lib/zip/output_stream.rb b/lib/zip/output_stream.rb index 58d2772c..a2290c5d 100644 --- a/lib/zip/output_stream.rb +++ b/lib/zip/output_stream.rb @@ -100,15 +100,15 @@ def put_next_entry( ) raise Error, 'zip stream is closed' if @closed - new_entry = if entry_name.kind_of?(Entry) - entry_name - else - Entry.new( - @file_name, entry_name.to_s, comment: comment, - extra: extra, compression_method: compression_method, - compression_level: level - ) - end + new_entry = + if entry_name.kind_of?(Entry) || entry_name.kind_of?(StreamableStream) + entry_name + else + Entry.new( + @file_name, entry_name.to_s, comment: comment, extra: extra, + compression_method: compression_method, compression_level: level + ) + end init_next_entry(new_entry) @current_entry = new_entry diff --git a/test/entry_test.rb b/test/entry_test.rb index 13d42963..f0e423f4 100644 --- a/test/entry_test.rb +++ b/test/entry_test.rb @@ -26,7 +26,10 @@ def test_constructor_and_getters assert_equal(TEST_COMPRESSIONMETHOD, entry.compression_method) assert_equal(TEST_NAME, entry.name) assert_equal(TEST_SIZE, entry.size) - assert_equal(TEST_TIME, entry.time) + + # Reverse times when testing because we need to use DOSTime#== for the + # comparison, not Time#==. + assert_equal(entry.time, TEST_TIME) end def test_is_directory_and_is_file diff --git a/test/file_test.rb b/test/file_test.rb index b93c4e0d..6476e798 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -95,7 +95,10 @@ def test_get_output_stream custom_entry_args[:compression_level], entry.compression_level ) assert_equal(custom_entry_args[:size], entry.size) - assert_equal(custom_entry_args[:time], entry.time) + + # Reverse times when testing because we need to use DOSTime#== for the + # comparison, not Time#==. + assert_equal(entry.time, custom_entry_args[:time]) zf.get_output_stream('entry.bin') do |os| os.write(::File.open('test/data/generated/5entry.zip', 'rb').read) @@ -110,6 +113,24 @@ def test_get_output_stream end end + def test_get_output_stream_with_entry + Dir.mktmpdir do |tmp| + test_zip = File.join(tmp, 'test.zip') + time = Time.new(1999, 12, 31) + + ::Zip::File.open(test_zip, create: true) do |zip| + entry = ::Zip::Entry.new(zip.name, 'entry.txt', time: time) + zip.get_output_stream(entry) { |out| out.puts 'CONTENT!' } + end + + ::Zip::File.open(test_zip) do |zip| + # Reverse times when testing because we need to use DOSTime#== for the + # comparison, not Time#==. + assert_equal(zip.get_entry('entry.txt').time, time) + end + end + end + def test_open_buffer_with_string data = File.read('test/data/rubycode.zip', mode: 'rb') string = data.dup From e2e0e23763f81a5074b438e5819196e5e6c25ac1 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 22 Jan 2022 07:34:00 +0000 Subject: [PATCH 381/469] Remove `File::add_buffer` from the API. Its functionality is now replicated in `File::open_buffer` but in a more secure way. --- lib/zip/file.rb | 12 ++---------- test/file_test.rb | 2 +- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 1aa34575..d080d6d5 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -126,19 +126,11 @@ def open(file_name, create: false, **options) end end - # Same as #open. But outputs data to a buffer instead of a file - def add_buffer - io = ::StringIO.new - zf = ::Zip::File.new(io, create: true, buffer: true) - yield zf - zf.write_buffer(io) - end - # Like #open, but reads zip archive contents from a String or open IO # stream, and outputs data to a buffer. # (This can be used to extract data from a # downloaded zip archive without first saving it to disk.) - def open_buffer(io, **options) + def open_buffer(io = ::StringIO.new, create: false, **options) unless IO_METHODS.map { |method| io.respond_to?(method) }.all? || io.kind_of?(String) raise 'Zip::File.open_buffer expects a String or IO-like argument' \ "(responds to #{IO_METHODS.join(', ')}). Found: #{io.class}" @@ -149,7 +141,7 @@ def open_buffer(io, **options) # https://github.com/rubyzip/rubyzip/issues/119 io.binmode if io.respond_to?(:binmode) - zf = ::Zip::File.new(io, create: true, buffer: true, **options) + zf = ::Zip::File.new(io, create: create, buffer: true, **options) return zf unless block_given? yield zf diff --git a/test/file_test.rb b/test/file_test.rb index 6476e798..c8be3dbf 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -16,7 +16,7 @@ def teardown def test_create_from_scratch_to_buffer comment = 'a short comment' - buffer = ::Zip::File.add_buffer do |zf| + buffer = ::Zip::File.open_buffer(create: true) do |zf| zf.get_output_stream('myFile') { |os| os.write 'myFile contains just this' } zf.mkdir('dir1') zf.comment = comment From 31e66885280054cf2e6199c9a5a27449cb4fbf75 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 22 Jan 2022 07:38:18 +0000 Subject: [PATCH 382/469] Remove unused private method `File#directory?`. This was a fairly horrible method anyway, for a number of reasons. It looked like a method that tested whether a name was a 'directory' name or not, and it did, but it also had some side effects where it would convert it *to* a directory name in some cases as well. Thankfully, nothing was using it any more, and as it was private we can lose it safely. Gone. --- lib/zip/file.rb | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index d080d6d5..cae4f973 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -358,18 +358,6 @@ def mkdir(entry_name, permission = 0o755) private - def directory?(new_entry, src_path) - path_is_directory = ::File.directory?(src_path) - if new_entry.directory? && !path_is_directory - raise ArgumentError, - "entry name '#{new_entry}' indicates directory entry, but " \ - "'#{src_path}' is not a directory" - elsif !new_entry.directory? && path_is_directory - new_entry.name += '/' - end - new_entry.directory? && path_is_directory - end - def check_entry_exists(entry_name, continue_on_exists_proc, proc_name) continue_on_exists_proc ||= proc { Zip.continue_on_exists_proc } return unless @cdir.include?(entry_name) From 05a17390697f183da4e22fc73e9b2a11451ef79f Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 22 Jan 2022 08:39:43 +0000 Subject: [PATCH 383/469] Properly test `File#mkdir`. --- test/file_test.rb | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/test/file_test.rb b/test/file_test.rb index c8be3dbf..5c04d1cb 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -413,6 +413,44 @@ def test_add_directory end end + def test_mkdir + buffer = ::Zip::File.open_buffer(create: true) do |zf| + # Add a directory with no slash. + zf.mkdir('dir') + + # Add it again. + assert_raises(Errno::EEXIST) do + zf.mkdir('dir') + end + + # Add it with a slash. + assert_raises(Errno::EEXIST) do + zf.mkdir('dir/') + end + + # Add a directory with a slash. + zf.mkdir('folder/') + + # Add it again. + assert_raises(Errno::EEXIST) do + zf.mkdir('folder/') + end + + # Add it without a slash. + assert_raises(Errno::EEXIST) do + zf.mkdir('folder') + end + end + + ::Zip::File.open_buffer(buffer) do |zf| + assert(zf.find_entry('dir/').directory?) + assert(zf.find_entry('dir').directory?) + + assert(zf.find_entry('folder/').directory?) + assert(zf.find_entry('folder').directory?) + end + end + def test_remove entry, *remaining = TEST_ZIP.entry_names From d2789dd0e353259817cf19ab8aabd40ebb2e2b29 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 6 Feb 2022 15:03:01 +0000 Subject: [PATCH 384/469] Add a note to the README about 2.3 compatibility. Closes #520. --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d2c98da9..368e9ad0 100644 --- a/README.md +++ b/README.md @@ -351,7 +351,15 @@ You can set multiple settings at the same time by using a block: ## Compatibility -Rubyzip is known to run on a number of platforms and under a number of different Ruby versions. Please see the table below for what we think the current situation is. Note: an empty cell means "unknown", not "does not work". +Rubyzip is known to run on a number of platforms and under a number of different Ruby versions. + +### Version 2.3.x + +Rubyzip 2.3 is known to work on MRI 2.4 to 3.1 on Linux and Mac, and JRuby and Truffleruby on Linux. There are known issues with Windows which have been fixed on the development branch. Please [let us know](https://github.com/rubyzip/rubyzip/pulls) if you know Rubyzip 2.3 works on a platform/Ruby combination not listed here, or [raise an issue](https://github.com/rubyzip/rubyzip/issues) if you see a failure where we think it should work. + +### Next (version 3.0.0) + +Please see the table below for what we think the current situation is. Note: an empty cell means "unknown", not "does not work". | OS/Ruby | 2.5 | 2.6 | 2.7 | 3.0 | 3.1 | 3.1 +YJIT | Head | Head +YJIT | JRuby 9.3.2.0 | JRuby Head | Truffleruby 21.3.0 | Truffleruby Head | |---------|-----|-----|-----|-----|-----|----------|------|-----------|----------------|------------|--------------------|------------------| From 243a66496a7e928eaab7f12b9dbcb4f1d6f75fdf Mon Sep 17 00:00:00 2001 From: naoto hamada Date: Wed, 6 Apr 2022 09:20:27 +0900 Subject: [PATCH 385/469] Fix indent --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 368e9ad0..4229d111 100644 --- a/README.md +++ b/README.md @@ -226,7 +226,7 @@ buffer = Zip::OutputStream.write_buffer do |out| unless [DOCUMENT_FILE_PATH, RELS_FILE_PATH].include?(e.name) out.put_next_entry(e.name) out.write e.get_input_stream.read - end + end end out.put_next_entry(DOCUMENT_FILE_PATH) From e07f01950792484e92a11edb17c46d2edc776984 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 23 Apr 2022 13:52:50 +0100 Subject: [PATCH 386/469] Improve the description of `InputStream#get_next_entry`. Documentation now refects the fact that the stream is positioned at the start of the entry data. --- lib/zip/input_stream.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index 66ffdd3e..e292ff3d 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -64,10 +64,10 @@ def close @archive_io.close end - # Returns a Entry object. It is necessary to call this - # method on a newly created InputStream before reading from - # the first entry in the archive. Returns nil when there are - # no more entries. + # Returns an Entry object and positions the stream at the beginning of + # the entry data. It is necessary to call this method on a newly created + # InputStream before reading from the first entry in the archive. + # Returns nil when there are no more entries. def get_next_entry unless @current_entry.nil? if @current_entry.incomplete? From ffa90a37cbb85844754bc7256c994b554703aeba Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 23 Apr 2022 13:54:09 +0100 Subject: [PATCH 387/469] README: improve the crypto documentation. Clean it up and provide a decryption example. --- README.md | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4229d111..12d18720 100644 --- a/README.md +++ b/README.md @@ -204,15 +204,23 @@ Any attempt to move about in a zip file opened with `Zip::InputStream` could res Rubyzip supports reading/writing zip files with traditional zip encryption (a.k.a. "ZipCrypto"). AES encryption is not yet supported. It can be used with buffer streams, e.g.: ```ruby -Zip::OutputStream.write_buffer( - ::StringIO.new, encrypter: Zip::TraditionalEncrypter.new('password') -) do |out| - out.put_next_entry("my_file.txt") - out.write my_data -end.string +# Writing. +enc = Zip::TraditionalEncrypter.new('password') +buffer = Zip::OutputStream.write_buffer(encrypter: enc) do |output| + output.put_next_entry("my_file.txt") + output.write my_data +end + +# Reading. +dec = Zip::TraditionalDecrypter.new('password') +Zip::InputStream.open(buffer, decrypter: dec) do |input| + entry = input.get_next_entry + puts "Contents of '#{entry.name}':" + puts input.read +end ``` -This is an experimental feature and the interface for encryption may change in future versions. +_This is an experimental feature and the interface for encryption may change in future versions._ ## Known issues From 8b87b0e20007c1b4ad8d4e6cc4e293ed8fa3db3d Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Mon, 13 Jun 2022 16:47:51 +0100 Subject: [PATCH 388/469] Implement `Zip::FileSystem::ZipFsFile#symlink?` --- lib/zip/filesystem/file.rb | 4 ++-- test/filesystem/file_nonmutating_test.rb | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/zip/filesystem/file.rb b/lib/zip/filesystem/file.rb index 7a93f7e2..d5597c2b 100644 --- a/lib/zip/filesystem/file.rb +++ b/lib/zip/filesystem/file.rb @@ -197,8 +197,8 @@ def chardev?(_filename) false end - def symlink?(_filename) - false + def symlink?(filename) + @mapped_zip.get_entry(filename).symlink? end def socket?(_filename) diff --git a/test/filesystem/file_nonmutating_test.rb b/test/filesystem/file_nonmutating_test.rb index 0e93b641..9c5d9d13 100644 --- a/test/filesystem/file_nonmutating_test.rb +++ b/test/filesystem/file_nonmutating_test.rb @@ -186,7 +186,9 @@ def test_blockdev? end def test_symlink? - assert_always_false(:symlink?) + zip_file = ::Zip::File.new('test/data/path_traversal/tuzovakaoff/symlink.zip') + assert(zip_file.file.symlink?('path')) + assert(!zip_file.file.symlink?('path/file.txt')) end def test_socket? From 451a04f7a26cb1166d142e2ba5641f1737162e75 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Tue, 14 Jun 2022 08:36:19 +0100 Subject: [PATCH 389/469] Test for `Errno::ENOENT` --- test/filesystem/file_nonmutating_test.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/filesystem/file_nonmutating_test.rb b/test/filesystem/file_nonmutating_test.rb index 9c5d9d13..7124cce2 100644 --- a/test/filesystem/file_nonmutating_test.rb +++ b/test/filesystem/file_nonmutating_test.rb @@ -189,6 +189,7 @@ def test_symlink? zip_file = ::Zip::File.new('test/data/path_traversal/tuzovakaoff/symlink.zip') assert(zip_file.file.symlink?('path')) assert(!zip_file.file.symlink?('path/file.txt')) + assert_e_n_o_e_n_t(:symlink?) end def test_socket? From 513ce5e5f7c7a1f6886a9554f91537da6a647475 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 18 Jun 2022 12:45:59 +0100 Subject: [PATCH 390/469] Remove unnecessary encoding change in tests for `File`. --- test/file_test.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/test/file_test.rb b/test/file_test.rb index 5c04d1cb..83a6c556 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -199,7 +199,6 @@ def test_open_buffer_close_does_not_change_file def test_open_buffer_with_io_and_block File.open('test/data/rubycode.zip') do |io| - io.set_encoding(Encoding::BINARY) # not strictly required but can be set Zip::File.open_buffer(io) do |zip_io| # left empty on purpose end From 48d6acf9cac5a57b6315941c4412ec56d41bfc20 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 18 Jun 2022 16:19:52 +0100 Subject: [PATCH 391/469] Ensure all streams passed to `File.new` are in `binmode`. Previously, only those streams that were passed to `new` by `open_buffer` were in the correct mode. --- lib/zip/file.rb | 5 ++--- test/file_test.rb | 10 ++++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index cae4f973..15c52ca4 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -83,6 +83,8 @@ def initialize(path_or_io, create: false, buffer: false, **options) @file_permissions = ::File.stat(@name).mode if buffer + # https://github.com/rubyzip/rubyzip/issues/119 + path_or_io.binmode if path_or_io.respond_to?(:binmode) @cdir.read_from_stream(path_or_io) else ::File.open(@name, 'rb') do |f| @@ -138,9 +140,6 @@ def open_buffer(io = ::StringIO.new, create: false, **options) io = ::StringIO.new(io) if io.kind_of?(::String) - # https://github.com/rubyzip/rubyzip/issues/119 - io.binmode if io.respond_to?(:binmode) - zf = ::Zip::File.new(io, create: create, buffer: true, **options) return zf unless block_given? diff --git a/test/file_test.rb b/test/file_test.rb index 83a6c556..82019f05 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -131,6 +131,15 @@ def test_get_output_stream_with_entry end end + def test_new_with_io_opened_non_binary_mode + File.open('test/data/test.xls') do |io| + refute(io.binmode?) # We open in non-binmode on purpose. + Zip::File.new(io, buffer: true) do |zip_io| + # left empty on purpose + end + end + end + def test_open_buffer_with_string data = File.read('test/data/rubycode.zip', mode: 'rb') string = data.dup @@ -199,6 +208,7 @@ def test_open_buffer_close_does_not_change_file def test_open_buffer_with_io_and_block File.open('test/data/rubycode.zip') do |io| + refute(io.binmode?) # We open in non-binmode on purpose. Zip::File.open_buffer(io) do |zip_io| # left empty on purpose end From e0e754ae65883ea63175c95f252493abf6e7d43e Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 6 Feb 2022 16:46:41 +0000 Subject: [PATCH 392/469] Switch how the `Entry::dirty` flag is used. Set it to true by default - because a new `Entry` is dirty by definition, having not been written yet. Then make sure that an `Entry` that is created by reading from a zip file is set as not dirty. --- lib/zip/entry.rb | 9 ++++----- lib/zip/file.rb | 2 -- lib/zip/streamable_stream.rb | 1 + test/entry_set_test.rb | 6 +++--- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 9764d91d..858b916e 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -49,9 +49,6 @@ def set_default_vars_values @unix_uid = nil @unix_gid = nil @unix_perms = nil - # @posix_acl = nil - # @ntfs_acl = nil - @dirty = false end def check_name(name) @@ -82,11 +79,11 @@ def initialize( @fstype = ::Zip::RUNNING_ON_WINDOWS ? ::Zip::FSTYPE_FAT : ::Zip::FSTYPE_UNIX @ftype = name_is_directory? ? :directory : :file + @dirty = true @zipfile = zipfile @comment = comment || '' @compression_method = compression_method || DEFLATED @compression_level = compression_level || ::Zip.default_compression - @compressed_size = compressed_size || 0 @crc = crc || 0 @size = size || 0 @@ -288,6 +285,7 @@ def unpack_local_entry(buf) end def read_local_entry(io) #:nodoc:all + @dirty = false # No changes at this point. @local_header_offset = io.tell static_sized_fields_buf = io.read(::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH) || '' @@ -433,6 +431,7 @@ def read_extra_field(buf, local: false) end def read_c_dir_entry(io) #:nodoc:all + @dirty = false # No changes at this point. static_sized_fields_buf = io.read(::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH) check_c_dir_entry_static_header_length(static_sized_fields_buf) unpack_c_dir_entry(static_sized_fields_buf) @@ -655,7 +654,7 @@ def get_raw_input_stream(&block) end def clean_up - # By default, do nothing + @dirty = false # Any changes are written at this point. end private diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 15c52ca4..116a940c 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -240,7 +240,6 @@ def add(entry, src_path, &continue_on_exists_proc) ) end new_entry.gather_fileinfo_from_srcpath(src_path) - new_entry.dirty = true @cdir << new_entry end @@ -291,7 +290,6 @@ def commit ::Zip::OutputStream.open(tmp_file) do |zos| @cdir.each do |e| e.write_to_zip_output_stream(zos) - e.dirty = false e.clean_up end zos.comment = comment diff --git a/lib/zip/streamable_stream.rb b/lib/zip/streamable_stream.rb index e823e002..2fea80c7 100644 --- a/lib/zip/streamable_stream.rb +++ b/lib/zip/streamable_stream.rb @@ -44,6 +44,7 @@ def write_to_zip_output_stream(output_stream) end def clean_up + super @temp_file.unlink end end diff --git a/test/entry_set_test.rb b/test/entry_set_test.rb index 42e93681..f653d836 100644 --- a/test/entry_set_test.rb +++ b/test/entry_set_test.rb @@ -62,14 +62,14 @@ def test_each count = 0 @zip_entry_set.each do |entry| assert(ZIP_ENTRIES.include?(entry)) - refute(entry.dirty) - entry.dirty = true # Check that entries can be changed in this block. + assert(entry.dirty) + entry.dirty = false # Check that entries can be changed in this block. count += 1 end assert_equal(ZIP_ENTRIES.size, count) @zip_entry_set.each do |entry| - assert(entry.dirty) + refute(entry.dirty) end end From 78a3cc596f217e18a98dc5fa2876295ed9714d61 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 6 Feb 2022 17:06:06 +0000 Subject: [PATCH 393/469] Make `Entry::zipfile` private. No need for it to be public, and especially not writeable. --- lib/zip/entry.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 858b916e..626eabb3 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -16,7 +16,7 @@ class Entry attr_accessor :comment, :compressed_size, :follow_symlinks, :name, :restore_ownership, :restore_permissions, :restore_times, - :size, :unix_gid, :unix_perms, :unix_uid, :zipfile + :size, :unix_gid, :unix_perms, :unix_uid attr_accessor :crc, :dirty, :external_file_attributes, :fstype, :gp_flags, :internal_file_attributes, :local_header_offset # :nodoc: From 30022510484922926e0d15ee768093b7ff9476c6 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 6 Feb 2022 17:46:25 +0000 Subject: [PATCH 394/469] Mark certain methods in `Entry` as making it dirty. This allows us to track which entries have changed without keeping a copy of all entries. I hope. --- lib/zip/entry.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 626eabb3..bc12664f 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -14,6 +14,20 @@ class Entry COMPRESSION_LEVEL_FAST_GPFLAG = 0b100 COMPRESSION_LEVEL_MAX_GPFLAG = 0b010 + # Mark this Entry as dirty if the supplied method is called. + def self.mark_dirty(*symbols) # :nodoc: + # Move the original method and call it after we've set the dirty flag. + symbols.each do |symbol| + orig_name = "orig_#{symbol}" + alias_method orig_name, symbol + + define_method(symbol) do |param| + @dirty = true + send(orig_name, param) + end + end + end + attr_accessor :comment, :compressed_size, :follow_symlinks, :name, :restore_ownership, :restore_permissions, :restore_times, :size, :unix_gid, :unix_perms, :unix_uid @@ -23,6 +37,9 @@ class Entry attr_reader :extra, :compression_level, :ftype, :filepath # :nodoc: + mark_dirty :comment=, :compressed_size=, :name=, :size=, + :unix_gid=, :unix_perms=, :unix_uid= + def set_default_vars_values @local_header_offset = 0 @local_header_size = nil # not known until local entry is created or read From 7b340d62a6eeb0b667fe7b49098f426d91bebadb Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 7 Feb 2022 15:24:55 +0000 Subject: [PATCH 395/469] Abstract marking as dirty into `Dirtyable` for reuse. --- lib/zip/dirtyable.rb | 32 ++++++++++++++++++++++++++++++++ lib/zip/entry.rb | 21 ++++++--------------- 2 files changed, 38 insertions(+), 15 deletions(-) create mode 100644 lib/zip/dirtyable.rb diff --git a/lib/zip/dirtyable.rb b/lib/zip/dirtyable.rb new file mode 100644 index 00000000..78b1c86a --- /dev/null +++ b/lib/zip/dirtyable.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Zip + module Dirtyable # :nodoc: + def initialize(dirty_on_create: true) + @dirty = dirty_on_create + end + + def dirty? + @dirty + end + + module ClassMethods + def mark_dirty(*symbols) # :nodoc: + # Move the original method and call it after we've set the dirty flag. + symbols.each do |symbol| + orig_name = "orig_#{symbol}" + alias_method orig_name, symbol + + define_method(symbol) do |param| + @dirty = true + send(orig_name, param) + end + end + end + end + + def self.included(base) + base.extend(ClassMethods) + end + end +end diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index bc12664f..27a40428 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -1,8 +1,13 @@ # frozen_string_literal: true require 'pathname' + +require_relative 'dirtyable' + module Zip class Entry + include Dirtyable + STORED = ::Zip::COMPRESSION_METHOD_STORE DEFLATED = ::Zip::COMPRESSION_METHOD_DEFLATE @@ -14,20 +19,6 @@ class Entry COMPRESSION_LEVEL_FAST_GPFLAG = 0b100 COMPRESSION_LEVEL_MAX_GPFLAG = 0b010 - # Mark this Entry as dirty if the supplied method is called. - def self.mark_dirty(*symbols) # :nodoc: - # Move the original method and call it after we've set the dirty flag. - symbols.each do |symbol| - orig_name = "orig_#{symbol}" - alias_method orig_name, symbol - - define_method(symbol) do |param| - @dirty = true - send(orig_name, param) - end - end - end - attr_accessor :comment, :compressed_size, :follow_symlinks, :name, :restore_ownership, :restore_permissions, :restore_times, :size, :unix_gid, :unix_perms, :unix_uid @@ -89,6 +80,7 @@ def initialize( compression_level: ::Zip.default_compression, time: ::Zip::DOSTime.now, extra: ::Zip::ExtraField.new ) + super() @name = name check_name(@name) @@ -96,7 +88,6 @@ def initialize( @fstype = ::Zip::RUNNING_ON_WINDOWS ? ::Zip::FSTYPE_FAT : ::Zip::FSTYPE_UNIX @ftype = name_is_directory? ? :directory : :file - @dirty = true @zipfile = zipfile @comment = comment || '' @compression_method = compression_method || DEFLATED From 08641db9f8be2bba7d13d351294a92d11228aea7 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 7 Feb 2022 22:05:19 +0000 Subject: [PATCH 396/469] Make `CentralDirectory` dirtyable. --- lib/zip/central_directory.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/zip/central_directory.rb b/lib/zip/central_directory.rb index 171d0243..bad4100d 100644 --- a/lib/zip/central_directory.rb +++ b/lib/zip/central_directory.rb @@ -2,9 +2,12 @@ require 'forwardable' +require_relative 'dirtyable' + module Zip class CentralDirectory extend Forwardable + include Dirtyable END_OF_CD_SIG = 0x06054b50 ZIP64_END_OF_CD_SIG = 0x06064b50 @@ -23,8 +26,10 @@ class CentralDirectory :<<, :delete, :each, :entries, :find_entry, :glob, :include?, :size + mark_dirty :<<, :comment=, :delete + def initialize(entries = EntrySet.new, comment = '') #:nodoc: - super() + super(dirty_on_create: false) @entry_set = entries.kind_of?(EntrySet) ? entries : EntrySet.new(entries) @comment = comment end From 5cd1ef2910729009308cee747f1aeabfe0b850ef Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 7 Feb 2022 22:22:20 +0000 Subject: [PATCH 397/469] Use new dirty statuses to detect zip file changes. This also means that we no longer need to keep a copy of the original set of `Entry`s or the central directory comment to test for changes. For situations where a zip file has a lot of entries (e.g. #506) this means we save a lot of memory, and a lot of time constructing the zip file in memory. --- lib/zip/file.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 116a940c..25389db3 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -105,8 +105,6 @@ def initialize(path_or_io, create: false, buffer: false, **options) raise Error, "File #{@name} not found" end - @stored_entries = @cdir.entries.map(&:dup) - @stored_comment = @cdir.comment @restore_ownership = options[:restore_ownership] @restore_permissions = options[:restore_permissions] @restore_times = options[:restore_times] @@ -317,10 +315,13 @@ def close # Returns true if any changes has been made to this archive since # the previous commit def commit_required? + return true if @create || @cdir.dirty? + @cdir.each do |e| - return true if e.dirty + return true if e.dirty? end - comment != @stored_comment || entries != @stored_entries || @create + + false end # Searches for entry with the specified name. Returns nil if From 33dce510a6591f973682c498a6fab269c896ca18 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 7 Feb 2022 23:04:49 +0000 Subject: [PATCH 398/469] Remove `Entry#dirty=` as 'dirtyness' is now monitored internally. Had to round out some of the accessors that mark an `Entry` as dirty. --- lib/zip/entry.rb | 5 +++-- lib/zip/filesystem/file.rb | 1 - test/entry_set_test.rb | 8 +++++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 27a40428..786d65b3 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -23,12 +23,13 @@ class Entry :restore_ownership, :restore_permissions, :restore_times, :size, :unix_gid, :unix_perms, :unix_uid - attr_accessor :crc, :dirty, :external_file_attributes, :fstype, :gp_flags, + attr_accessor :crc, :external_file_attributes, :fstype, :gp_flags, :internal_file_attributes, :local_header_offset # :nodoc: attr_reader :extra, :compression_level, :ftype, :filepath # :nodoc: - mark_dirty :comment=, :compressed_size=, :name=, :size=, + mark_dirty :comment=, :compressed_size=, :external_file_attributes=, + :fstype=, :gp_flags=, :name=, :size=, :unix_gid=, :unix_perms=, :unix_uid= def set_default_vars_values diff --git a/lib/zip/filesystem/file.rb b/lib/zip/filesystem/file.rb index d5597c2b..8ee6d06b 100644 --- a/lib/zip/filesystem/file.rb +++ b/lib/zip/filesystem/file.rb @@ -124,7 +124,6 @@ def chmod(mode, *filenames) e.fstype = FSTYPE_UNIX # Force conversion filesystem type to unix. e.unix_perms = mode e.external_file_attributes = mode << 16 - e.dirty = true end filenames.size end diff --git a/test/entry_set_test.rb b/test/entry_set_test.rb index f653d836..c5b27b73 100644 --- a/test/entry_set_test.rb +++ b/test/entry_set_test.rb @@ -60,16 +60,18 @@ def test_delete def test_each # Used each instead each_with_index due the bug in jRuby count = 0 + new_size = 200 @zip_entry_set.each do |entry| assert(ZIP_ENTRIES.include?(entry)) - assert(entry.dirty) - entry.dirty = false # Check that entries can be changed in this block. + entry.clean_up # Start from a "saved" state. + entry.size = new_size # Check that entries can be changed in this block. count += 1 end assert_equal(ZIP_ENTRIES.size, count) @zip_entry_set.each do |entry| - refute(entry.dirty) + assert_equal(new_size, entry.size) + assert(entry.dirty?) # Size was changed. end end From 307fc6c6e95e89824f494afdd2a0d56d77c0f4e1 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 21 Feb 2022 09:15:50 +0000 Subject: [PATCH 399/469] Mark other mutating methods in `Entry` as dirty. Also, remove `Entry#extra=` as it makes no sense (and wasn't even being tested). And remove slightly odd test that was assuming an archive would not be changed if its utime was changed - even if it was being changed back immediately. This test was merely confirming that we weren't catching timestamp changes correctly. --- .rubocop_todo.yml | 2 +- lib/zip/entry.rb | 10 ++-------- test/filesystem/file_nonmutating_test.rb | 9 --------- 3 files changed, 3 insertions(+), 18 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 4406c97d..387a9cd6 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -20,7 +20,7 @@ Lint/MissingSuper: # Offense count: 5 # Configuration parameters: CountComments, CountAsOne. Metrics/ClassLength: - Max: 620 + Max: 630 # Offense count: 21 # Configuration parameters: IgnoredMethods. diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 786d65b3..36bb2dea 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -118,14 +118,6 @@ def incomplete? gp_flags & 8 == 8 end - def extra=(field) - @extra = if field.nil? - ExtraField.new - else - field.kind_of?(ExtraField) ? field : ExtraField.new(field.to_s) - end - end - def time if @extra['UniversalTime'] && !@extra['UniversalTime'].mtime.nil? @extra['UniversalTime'].mtime @@ -141,6 +133,7 @@ def time alias mtime time def time=(value) + @dirty = true unless @extra.member?('UniversalTime') || @extra.member?('NTFS') @extra.create('UniversalTime') end @@ -157,6 +150,7 @@ def compression_method end def compression_method=(method) + @dirty = true @compression_method = (@ftype == :directory ? STORED : method) end diff --git a/test/filesystem/file_nonmutating_test.rb b/test/filesystem/file_nonmutating_test.rb index 7124cce2..e6b1f132 100644 --- a/test/filesystem/file_nonmutating_test.rb +++ b/test/filesystem/file_nonmutating_test.rb @@ -152,15 +152,6 @@ def test_join assert_equal('a/b/c/d', @zip_file.file.join('a', 'b', 'c', 'd')) end - def test_utime - t_now = ::Zip::DOSTime.now - t_bak = @zip_file.file.mtime('file1') - @zip_file.file.utime(t_now, 'file1') - assert_equal(t_now, @zip_file.file.mtime('file1')) - @zip_file.file.utime(t_bak, 'file1') - assert_equal(t_bak, @zip_file.file.mtime('file1')) - end - def assert_always_false(operation) assert(!@zip_file.file.send(operation, 'noSuchFile')) assert(!@zip_file.file.send(operation, 'file1')) From ae0262df2e75fab1ce1ecbc1095c33032e1241d6 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 23 Feb 2022 10:13:13 +0000 Subject: [PATCH 400/469] Add `Entry#zip64?` as a better way detect Zip64 entries. --- lib/zip/central_directory.rb | 4 ++-- lib/zip/entry.rb | 6 +++++- test/local_entry_test.rb | 4 ++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/zip/central_directory.rb b/lib/zip/central_directory.rb index bad4100d..146c444f 100644 --- a/lib/zip/central_directory.rb +++ b/lib/zip/central_directory.rb @@ -47,7 +47,7 @@ def write_to_stream(io) #:nodoc: if ::Zip.write_zip64_support need_zip64_eocd = cdir_offset > 0xFFFFFFFF || cdir_size > 0xFFFFFFFF \ || @entry_set.size > 0xFFFF - need_zip64_eocd ||= @entry_set.any? { |entry| entry.extra['Zip64'] } + need_zip64_eocd ||= @entry_set.any?(&:zip64?) if need_zip64_eocd write_64_e_o_c_d(io, cdir_offset, cdir_size) write_64_eocd_locator(io, eocd_offset) @@ -185,7 +185,7 @@ def read_central_directory_entries(io) #:nodoc: entry = Entry.read_c_dir_entry(io) next unless entry - offset = if entry.extra['Zip64'] + offset = if entry.zip64? entry.extra['Zip64'].relative_header_offset else entry.local_header_offset diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 36bb2dea..8f6714c6 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -154,6 +154,10 @@ def compression_method=(method) @compression_method = (@ftype == :directory ? STORED : method) end + def zip64? + !@extra['Zip64'].nil? + end + def file_type_is?(type) raise InternalError, "current filetype is unknown: #{inspect}" unless @ftype @@ -721,7 +725,7 @@ def create_symlink(dest_path) # apply missing data from the zip64 extra information field, if present # (required when file sizes exceed 2**32, but can be used for all files) def parse_zip64_extra(for_local_header) #:nodoc:all - return if @extra['Zip64'].nil? + return unless zip64? if for_local_header @size, @compressed_size = @extra['Zip64'].parse(@size, @compressed_size) diff --git a/test/local_entry_test.rb b/test/local_entry_test.rb index 3cf21e62..f934d49d 100644 --- a/test/local_entry_test.rb +++ b/test/local_entry_test.rb @@ -122,13 +122,13 @@ def test_rewrite_local_header64 buf1 = StringIO.new entry = ::Zip::Entry.new('file.zip', 'entry_name') entry.write_local_entry(buf1) - assert(entry.extra['Zip64'].nil?, 'zip64 extra is unnecessarily present') + refute(entry.zip64?, 'zip64 extra is unnecessarily present') buf2 = StringIO.new entry.size = 0x123456789ABCDEF0 entry.compressed_size = 0x0123456789ABCDEF entry.write_local_entry(buf2, rewrite: true) - refute_nil(entry.extra['Zip64']) + assert(entry.zip64?) refute_equal(buf1.size, 0) assert_equal(buf1.size, buf2.size) # it can't grow, or we'd clobber file data end From d6482bd56736683912258e632efdd88deadbd454 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 23 Feb 2022 12:39:01 +0000 Subject: [PATCH 401/469] Generalize `Entry#time`. So we can use it for `atime`, `ctime` and `utime` as well. --- lib/zip/entry.rb | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 8f6714c6..f32f71dd 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -118,16 +118,17 @@ def incomplete? gp_flags & 8 == 8 end - def time - if @extra['UniversalTime'] && !@extra['UniversalTime'].mtime.nil? - @extra['UniversalTime'].mtime - elsif @extra['NTFS'] && !@extra['NTFS'].mtime.nil? - @extra['NTFS'].mtime - else - # Standard time field in central directory has local time - # under archive creator. Then, we can't get timezone. - @time - end + def time(component: :mtime) + time = + if @extra['UniversalTime'] + @extra['UniversalTime'].send(component) + elsif @extra['NTFS'] + @extra['NTFS'].send(component) + end + + # Standard time field in central directory has local time + # under archive creator. Then, we can't get timezone. + time || (@time if component == :mtime) end alias mtime time From 62ed397b1afcb941c88c69836d841c2d70d2ddbb Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 23 Feb 2022 12:40:47 +0000 Subject: [PATCH 402/469] Generalize `Entry#time=`. So we can use it for `atime=`, `ctime=` and `utime=` as well. --- lib/zip/entry.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index f32f71dd..92e2c2f5 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -133,15 +133,16 @@ def time(component: :mtime) alias mtime time - def time=(value) + def time=(value, component: :mtime) @dirty = true unless @extra.member?('UniversalTime') || @extra.member?('NTFS') @extra.create('UniversalTime') end value = DOSTime.from_time(value) - (@extra['UniversalTime'] || @extra['NTFS']).mtime = value - @time = value + comp = "#{component}=" unless component.to_s.end_with?('=') + (@extra['UniversalTime'] || @extra['NTFS']).send(comp, value) + @time = value if component == :mtime end def compression_method From fff1f8ea8ac472ff01538245749fdf1d5808de36 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 23 Feb 2022 12:54:56 +0000 Subject: [PATCH 403/469] Add `Entry#mtime=` as an alias of `Entry#time=`. --- lib/zip/entry.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 92e2c2f5..28ce277f 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -145,6 +145,8 @@ def time=(value, component: :mtime) @time = value if component == :mtime end + alias mtime= time= + def compression_method return STORED if @ftype == :directory || @compression_level == 0 From 466383ff1a21e366c42192883de0edd778384174 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 23 Feb 2022 21:49:34 +0000 Subject: [PATCH 404/469] Add other `Entry` time methods and test them all. --- .rubocop_todo.yml | 2 +- lib/zip/entry.rb | 16 +++++++++++++++ test/entry_test.rb | 50 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 387a9cd6..2b3f89b1 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -20,7 +20,7 @@ Lint/MissingSuper: # Offense count: 5 # Configuration parameters: CountComments, CountAsOne. Metrics/ClassLength: - Max: 630 + Max: 650 # Offense count: 21 # Configuration parameters: IgnoredMethods. diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 28ce277f..732039ce 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -133,6 +133,14 @@ def time(component: :mtime) alias mtime time + def atime + time(component: :atime) + end + + def ctime + time(component: :ctime) + end + def time=(value, component: :mtime) @dirty = true unless @extra.member?('UniversalTime') || @extra.member?('NTFS') @@ -147,6 +155,14 @@ def time=(value, component: :mtime) alias mtime= time= + def atime=(value) + send(:time=, value, component: :atime) + end + + def ctime=(value) + send(:time=, value, component: :ctime) + end + def compression_method return STORED if @ftype == :directory || @compression_level == 0 diff --git a/test/entry_test.rb b/test/entry_test.rb index f0e423f4..0ecf2f4a 100644 --- a/test/entry_test.rb +++ b/test/entry_test.rb @@ -281,6 +281,56 @@ def test_set_time_as_dos_time assert(entry.time.kind_of?(::Zip::DOSTime)) end + def test_atime + entry = ::Zip::Entry.new + time = Time.new(1999, 12, 31, 23, 59, 59) + + entry.atime = time + assert(entry.dirty?) + assert_equal(::Zip::DOSTime.from_time(time), entry.atime) + refute_equal(entry.time, entry.atime) + assert(entry.atime.kind_of?(::Zip::DOSTime)) + assert_nil(entry.ctime) + end + + def test_ctime + entry = ::Zip::Entry.new + time = Time.new(1999, 12, 31, 23, 59, 59) + + entry.ctime = time + assert(entry.dirty?) + assert_equal(::Zip::DOSTime.from_time(time), entry.ctime) + refute_equal(entry.time, entry.ctime) + assert(entry.ctime.kind_of?(::Zip::DOSTime)) + assert_nil(entry.atime) + end + + def test_mtime + entry = ::Zip::Entry.new + time = Time.new(1999, 12, 31, 23, 59, 59) + + entry.mtime = time + assert(entry.dirty?) + assert_equal(::Zip::DOSTime.from_time(time), entry.mtime) + assert_equal(entry.time, entry.mtime) + assert(entry.mtime.kind_of?(::Zip::DOSTime)) + assert_nil(entry.atime) + assert_nil(entry.ctime) + end + + def test_time + entry = ::Zip::Entry.new + time = Time.new(1999, 12, 31, 23, 59, 59) + + entry.time = time + assert(entry.dirty?) + assert_equal(::Zip::DOSTime.from_time(time), entry.time) + assert_equal(entry.mtime, entry.time) + assert(entry.time.kind_of?(::Zip::DOSTime)) + assert_nil(entry.atime) + assert_nil(entry.ctime) + end + def test_ensure_entry_time_set_to_file_mtime entry = ::Zip::Entry.new entry.gather_fileinfo_from_srcpath('test/data/mimetype') From 6486047d5fdca6b331401ec487cff8ac65badcaf Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 23 Feb 2022 21:50:23 +0000 Subject: [PATCH 405/469] Use the new `Entry` time methods in `Filesystem::File`. --- lib/zip/filesystem/file.rb | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/lib/zip/filesystem/file.rb b/lib/zip/filesystem/file.rb index 8ee6d06b..cb094701 100644 --- a/lib/zip/filesystem/file.rb +++ b/lib/zip/filesystem/file.rb @@ -167,21 +167,11 @@ def mtime(filename) end def atime(filename) - e = find_entry(filename) - if e.extra.member? 'UniversalTime' - e.extra['UniversalTime'].atime - elsif e.extra.member? 'NTFS' - e.extra['NTFS'].atime - end + @mapped_zip.get_entry(filename).atime end def ctime(filename) - e = find_entry(filename) - if e.extra.member? 'UniversalTime' - e.extra['UniversalTime'].ctime - elsif e.extra.member? 'NTFS' - e.extra['NTFS'].ctime - end + @mapped_zip.get_entry(filename).ctime end def pipe?(_filename) From c243b4429af9a29521b5ca2e99654e127684eadb Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 25 Jun 2022 08:48:23 +0100 Subject: [PATCH 406/469] Factor out the code in `File` to init the CDir. This allows us to reuse it without overwriting any options passed to `File`. --- lib/zip/file.rb | 61 ++++++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 25389db3..93a76d33 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -75,35 +75,8 @@ def initialize(path_or_io, create: false, buffer: false, **options) .merge(options) @name = path_or_io.respond_to?(:path) ? path_or_io.path : path_or_io @create = create ? true : false # allow any truthy value to mean true - @cdir = ::Zip::CentralDirectory.new - - if ::File.size?(@name.to_s) - # There is a file, which exists, that is associated with this zip. - @create = false - @file_permissions = ::File.stat(@name).mode - if buffer - # https://github.com/rubyzip/rubyzip/issues/119 - path_or_io.binmode if path_or_io.respond_to?(:binmode) - @cdir.read_from_stream(path_or_io) - else - ::File.open(@name, 'rb') do |f| - @cdir.read_from_stream(f) - end - end - elsif buffer && path_or_io.size > 0 - # This zip is probably a non-empty StringIO. - @create = false - @cdir.read_from_stream(path_or_io) - elsif !@create && ::File.zero?(@name) - # A file exists, but it is empty, and we've said we're - # NOT creating a new zip. - raise Error, "File #{@name} has zero size. Did you mean to pass the create flag?" - elsif !@create - # If we get here, and we're not creating a new zip, then - # everything is wrong. - raise Error, "File #{@name} not found" - end + initialize_cdir(path_or_io, buffer: buffer) @restore_ownership = options[:restore_ownership] @restore_permissions = options[:restore_permissions] @@ -356,6 +329,38 @@ def mkdir(entry_name, permission = 0o755) private + def initialize_cdir(path_or_io, buffer: false) + @cdir = ::Zip::CentralDirectory.new + + if ::File.size?(@name.to_s) + # There is a file, which exists, that is associated with this zip. + @create = false + @file_permissions = ::File.stat(@name).mode + + if buffer + # https://github.com/rubyzip/rubyzip/issues/119 + path_or_io.binmode if path_or_io.respond_to?(:binmode) + @cdir.read_from_stream(path_or_io) + else + ::File.open(@name, 'rb') do |f| + @cdir.read_from_stream(f) + end + end + elsif buffer && path_or_io.size > 0 + # This zip is probably a non-empty StringIO. + @create = false + @cdir.read_from_stream(path_or_io) + elsif !@create && ::File.zero?(@name) + # A file exists, but it is empty, and we've said we're + # NOT creating a new zip. + raise Error, "File #{@name} has zero size. Did you mean to pass the create flag?" + elsif !@create + # If we get here, and we're not creating a new zip, then + # everything is wrong. + raise Error, "File #{@name} not found" + end + end + def check_entry_exists(entry_name, continue_on_exists_proc, proc_name) continue_on_exists_proc ||= proc { Zip.continue_on_exists_proc } return unless @cdir.include?(entry_name) From 14ff11ba05795c289cb1ea18b32287aa654d3cb1 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 25 Jun 2022 08:51:32 +0100 Subject: [PATCH 407/469] Re-initialize CDir after a `commit`. Using the factored-out code preserves options set in `File`. Fixes #529. --- lib/zip/file.rb | 2 +- test/file_test.rb | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 93a76d33..89a098f9 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -267,7 +267,7 @@ def commit end true end - initialize(name) + initialize_cdir(@name) end # Write buffer write changes to buffer and return diff --git a/test/file_test.rb b/test/file_test.rb index 82019f05..e78eb8a1 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -640,6 +640,16 @@ def test_commit assert_equal(res, true) end + def test_commit_preserves_options + zip_file = 'test/data/generated/preserve_options.zip' + ::Zip::File.open(zip_file, create: true, compression_level: 8) do |zf| + assert(zf.commit_required?) + zf.commit + assert_equal(8, zf.instance_variable_get(:@compression_level)) + refute(zf.commit_required?) + end + end + def test_double_commit(filename = 'test/data/generated/double_commit_test.zip') ::FileUtils.touch('test/data/generated/test_double_commit1.txt') ::FileUtils.touch('test/data/generated/test_double_commit2.txt') From 708b7f53932071b552b746b6d66b6c21b5549a6e Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 25 Jun 2022 08:53:35 +0100 Subject: [PATCH 408/469] Add a couple more checks in the tests for double `commit`s. Just ensure that a `commit` really does stick with both new and edited zip files. --- test/file_test.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/file_test.rb b/test/file_test.rb index e78eb8a1..106e69ab 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -656,8 +656,11 @@ def test_double_commit(filename = 'test/data/generated/double_commit_test.zip') zf = ::Zip::File.open(filename, create: true) zf.add('test1.txt', 'test/data/generated/test_double_commit1.txt') zf.commit + refute(zf.commit_required?) zf.add('test2.txt', 'test/data/generated/test_double_commit2.txt') + assert(zf.commit_required?) zf.commit + refute(zf.commit_required?) zf.close zf2 = ::Zip::File.open(filename) refute_nil(zf2.entries.detect { |e| e.name == 'test1.txt' }) From 6f1ad8b37dd360770cc34c378f71b5f0f8caaeca Mon Sep 17 00:00:00 2001 From: Brian Williams Date: Thu, 4 Aug 2022 13:53:33 -0500 Subject: [PATCH 409/469] Fix unraised error on encrypted archives --- lib/zip/input_stream.rb | 2 +- test/input_stream_test.rb | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index e292ff3d..e0739a89 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -144,7 +144,7 @@ def open_entry @current_entry = ::Zip::Entry.read_local_entry(@archive_io) return if @current_entry.nil? - if @current_entry.encrypted? && @decrypter.kind_of?(NullEncrypter) + if @current_entry.encrypted? && @decrypter.kind_of?(NullDecrypter) raise Error, 'A password is required to decode this zip file' end diff --git a/test/input_stream_test.rb b/test/input_stream_test.rb index 773381f8..293483ce 100644 --- a/test/input_stream_test.rb +++ b/test/input_stream_test.rb @@ -91,6 +91,14 @@ def test_open_split_archive_raises_error end end + def test_open_encrypted_archive_raises_error + ::Zip::InputStream.open('test/data/zipWithEncryption.zip') do |zis| + assert_raises(::Zip::Error) do + zis.get_next_entry + end + end + end + def test_size_no_entry zis = ::Zip::InputStream.open(TestZipFile::TEST_ZIP2.zip_name) assert_nil(zis.size) From 08391da4d51feac37862194c7a3f9c41f2ce2e14 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 13 Aug 2022 22:09:55 +0100 Subject: [PATCH 410/469] Ensure that `Entry.ftype` is correct via `InputStream`. When reading an archive with `InputStream`, `Entry.ftype` was returning `:file` for all entries, even if they were a directory. This is due to various side-effects in many methods in `Entry`. This commit fixes the behaviour, but not the side-effects. Fixes #533. --- lib/zip/entry.rb | 29 +++++++++++++++++------------ test/input_stream_test.rb | 10 ++++++++++ 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 732039ce..bfbf4b8b 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -26,7 +26,7 @@ class Entry attr_accessor :crc, :external_file_attributes, :fstype, :gp_flags, :internal_file_attributes, :local_header_offset # :nodoc: - attr_reader :extra, :compression_level, :ftype, :filepath # :nodoc: + attr_reader :extra, :compression_level, :filepath # :nodoc: mark_dirty :comment=, :compressed_size=, :external_file_attributes=, :fstype=, :gp_flags=, :name=, :size=, @@ -87,7 +87,6 @@ def initialize( set_default_vars_values @fstype = ::Zip::RUNNING_ON_WINDOWS ? ::Zip::FSTYPE_FAT : ::Zip::FSTYPE_UNIX - @ftype = name_is_directory? ? :directory : :file @zipfile = zipfile @comment = comment || '' @@ -164,14 +163,14 @@ def ctime=(value) end def compression_method - return STORED if @ftype == :directory || @compression_level == 0 + return STORED if ftype == :directory || @compression_level == 0 @compression_method end def compression_method=(method) @dirty = true - @compression_method = (@ftype == :directory ? STORED : method) + @compression_method = (ftype == :directory ? STORED : method) end def zip64? @@ -179,9 +178,11 @@ def zip64? end def file_type_is?(type) - raise InternalError, "current filetype is unknown: #{inspect}" unless @ftype + ftype == type + end - @ftype == type + def ftype # :nodoc: + @ftype ||= name_is_directory? ? :directory : :file end # Dynamic checkers @@ -263,7 +264,7 @@ def extract(dest_path = nil, &block) raise "unknown file type #{inspect}" unless directory? || file? || symlink? - __send__("create_#{@ftype}", dest_path, &block) + __send__("create_#{ftype}", dest_path, &block) self end @@ -340,6 +341,10 @@ def read_local_entry(io) #:nodoc:all end @name.tr!('\\', '/') # Normalise filepath separators after encoding set. + # We need to do this here because `initialize` has so many side-effects. + # :-( + @ftype = name_is_directory? ? :directory : :file + extra = io.read(@extra_length) if extra && extra.bytesize != @extra_length raise ::Zip::Error, 'Truncated local zip entry header' @@ -554,7 +559,7 @@ def write_c_dir_entry(io) #:nodoc:all prep_zip64_extra(false) case @fstype when ::Zip::FSTYPE_UNIX - ft = case @ftype + ft = case ftype when :file @unix_perms ||= 0o644 ::Zip::FILE_TYPE_FILE @@ -594,11 +599,11 @@ def <=>(other) # Returns an IO like object for the given ZipEntry. # Warning: may behave weird with symlinks. def get_input_stream(&block) - if @ftype == :directory + if ftype == :directory yield ::Zip::NullInputStream if block ::Zip::NullInputStream elsif @filepath - case @ftype + case ftype when :file ::File.open(@filepath, 'rb', &block) when :symlink @@ -607,7 +612,7 @@ def get_input_stream(&block) yield(stringio) if block stringio else - raise "unknown @file_type #{@ftype}" + raise "unknown @file_type #{ftype}" end else zis = ::Zip::InputStream.new(@zipfile, offset: local_header_offset) @@ -654,7 +659,7 @@ def gather_fileinfo_from_srcpath(src_path) # :nodoc: end def write_to_zip_output_stream(zip_output_stream) #:nodoc:all - if @ftype == :directory + if ftype == :directory zip_output_stream.put_next_entry(self) elsif @filepath zip_output_stream.put_next_entry(self) diff --git a/test/input_stream_test.rb b/test/input_stream_test.rb index 293483ce..6500e5eb 100644 --- a/test/input_stream_test.rb +++ b/test/input_stream_test.rb @@ -111,6 +111,16 @@ def test_size_with_entry end end + def test_get_entry_ftypes + ::Zip::InputStream.open(TestZipFile::TEST_ZIP4.zip_name) do |zis| + entry = zis.get_next_entry + assert_equal(:file, entry.ftype) + + entry = zis.get_next_entry + assert_equal(:directory, entry.ftype) + end + end + def test_incomplete_reads ::Zip::InputStream.open(TestZipFile::TEST_ZIP2.zip_name) do |zis| entry = zis.get_next_entry # longAscii.txt From 2e4dd9e0aa6194784f608211928b7d16edb5b8e6 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 27 Nov 2021 11:02:29 +0000 Subject: [PATCH 411/469] Improve the message for CompressionMethodError. Convert the compression method number into a meaningful text representation, e.g., "BZIP2" instead of "12". --- lib/zip/errors.rb | 14 +++++++++++++- lib/zip/input_stream.rb | 3 +-- lib/zip/output_stream.rb | 3 +-- test/bzip2_support_test.rb | 7 ++++++- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/lib/zip/errors.rb b/lib/zip/errors.rb index 66ae5356..3e1895a1 100644 --- a/lib/zip/errors.rb +++ b/lib/zip/errors.rb @@ -4,11 +4,23 @@ module Zip class Error < StandardError; end class EntryExistsError < Error; end class DestinationFileExistsError < Error; end - class CompressionMethodError < Error; end class EntryNameError < Error; end class EntrySizeError < Error; end class InternalError < Error; end class GPFBit3Error < Error; end class DecompressionError < Error; end class SplitArchiveError < Error; end + + class CompressionMethodError < Error + attr_reader :compression_method + + def initialize(method) + super() + @compression_method = method + end + + def message + "Unsupported compression method: #{COMPRESSION_METHODS[@compression_method]}." + end + end end diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index e0739a89..a1f29294 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -185,8 +185,7 @@ def get_decompressor @current_entry.compression_method ) if decompressor_class.nil? - raise ::Zip::CompressionMethodError, - "Unsupported compression method #{@current_entry.compression_method}" + raise ::Zip::CompressionMethodError, @current_entry.compression_method end decompressor_class.new(@decrypted_io, decompressed_size) diff --git a/lib/zip/output_stream.rb b/lib/zip/output_stream.rb index a2290c5d..0c4e93c8 100644 --- a/lib/zip/output_stream.rb +++ b/lib/zip/output_stream.rb @@ -170,8 +170,7 @@ def get_compressor(entry) when Entry::STORED ::Zip::PassThruCompressor.new(@output_stream) else - raise ::Zip::CompressionMethodError, - "Invalid compression method: '#{entry.compression_method}'" + raise ::Zip::CompressionMethodError, entry.compression_method end end diff --git a/test/bzip2_support_test.rb b/test/bzip2_support_test.rb index 119d1c0d..91c954a2 100644 --- a/test/bzip2_support_test.rb +++ b/test/bzip2_support_test.rb @@ -7,7 +7,12 @@ class Bzip2SupportTest < MiniTest::Test def test_read Zip::InputStream.open(BZIP2_ZIP_TEST_FILE) do |zis| - assert_raises(Zip::CompressionMethodError) { zis.get_next_entry } + error = assert_raises(Zip::CompressionMethodError) do + zis.get_next_entry + end + + assert_equal(12, error.compression_method) + assert_match(/BZIP2/, error.message) end end end From 03a9ee6b8a74aca0c46f7ed0123ab1e4bb7d96d4 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 28 Nov 2021 16:51:06 +0000 Subject: [PATCH 412/469] Rename `GPFBit3Error` to `StreamingError`. `GPFBit3Error` doesn't really mean anything to the general user, and it's not descriptive of the issue at hand. This error is raised when a zip file cannot be streamed via `InputStream`, so `StreamingError` makes more sense. Also standardize the error message while we're about it. --- README.md | 2 +- lib/zip/errors.rb | 16 +++++++++++++++- lib/zip/input_stream.rb | 12 ++---------- test/file_test.rb | 6 +++++- test/input_stream_test.rb | 8 ++++++-- 5 files changed, 29 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 12d18720..2dca73c5 100644 --- a/README.md +++ b/README.md @@ -185,7 +185,7 @@ There is one exception where it can not work however, and this is if the file do > If bit 3 (0x08) of the general-purpose flags field is set, then the CRC-32 and file sizes are not known when the header is written. The fields in the local header are filled with zero, and the CRC-32 and size are appended in a 12-byte structure (optionally preceded by a 4-byte signature) immediately after the compressed data. -If `Zip::InputStream` finds such an entry in the zip archive it will raise an exception (`Zip::GPFBit3Error`). +If `Zip::InputStream` finds such an entry in the zip archive it will raise an exception (`Zip::StreamingError`). `Zip::InputStream` is not designed to be used for random access in a zip file. When performing any operations on an entry that you are accessing via `Zip::InputStream.get_next_entry` then you should complete any such operations before the next call to `get_next_entry`. diff --git a/lib/zip/errors.rb b/lib/zip/errors.rb index 3e1895a1..fd10022d 100644 --- a/lib/zip/errors.rb +++ b/lib/zip/errors.rb @@ -7,7 +7,6 @@ class DestinationFileExistsError < Error; end class EntryNameError < Error; end class EntrySizeError < Error; end class InternalError < Error; end - class GPFBit3Error < Error; end class DecompressionError < Error; end class SplitArchiveError < Error; end @@ -23,4 +22,19 @@ def message "Unsupported compression method: #{COMPRESSION_METHODS[@compression_method]}." end end + + class StreamingError < Error + attr_reader :entry + + def initialize(entry) + super() + @entry = entry + end + + def message + "The local header of this entry ('#{@entry.name}') does not contain " \ + 'the correct metadata for `Zip::InputStream` to be able to ' \ + 'uncompress it. Please use `Zip::File` instead of `Zip::InputStream`.' + end + end end diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index a1f29294..f48466c0 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -70,12 +70,7 @@ def close # Returns nil when there are no more entries. def get_next_entry unless @current_entry.nil? - if @current_entry.incomplete? - raise GPFBit3Error, - 'It is not possible to get complete info from the local ' \ - 'header to extract this entry (GP flags bit 3 is set). ' \ - 'Please use `Zip::File` instead of `Zip::InputStream`.' - end + raise StreamingError, @current_entry if @current_entry.incomplete? @archive_io.seek(@current_entry.next_header_offset, IO::SEEK_SET) end @@ -151,10 +146,7 @@ def open_entry if @current_entry.incomplete? && @current_entry.compressed_size == 0 \ && !@complete_entry - raise GPFBit3Error, - 'It is not possible to get complete info from the local ' \ - 'header to extract this entry (GP flags bit 3 is set). ' \ - 'Please use `Zip::File` instead of `Zip::InputStream`.' + raise StreamingError, @current_entry end @decrypted_io = get_decrypted_io diff --git a/test/file_test.rb b/test/file_test.rb index 106e69ab..dfc48c81 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -46,9 +46,13 @@ def test_create_from_scratch def test_get_input_stream_stored_with_gpflag_bit3 ::Zip::File.open('test/data/gpbit3stored.zip') do |zf| zis = zf.get_input_stream('file1.txt') - assert_raises(::Zip::GPFBit3Error) do + + error = assert_raises(::Zip::StreamingError) do zis.get_next_entry end + assert_match(/file1\.txt/, error.message) + assert_equal('file1.txt', error.entry.name) + zf.get_input_stream('file2.txt') end end diff --git a/test/input_stream_test.rb b/test/input_stream_test.rb index 6500e5eb..becb9336 100644 --- a/test/input_stream_test.rb +++ b/test/input_stream_test.rb @@ -69,17 +69,21 @@ def test_open_io_like_with_block def test_open_file_with_gp3bit_set ::Zip::InputStream.open('test/data/gpbit3stored.zip') do |zis| - assert_raises(::Zip::GPFBit3Error) do + error = assert_raises(::Zip::StreamingError) do zis.get_next_entry end + assert_match(/file1\.txt/, error.message) + assert_equal('file1.txt', error.entry.name) end end def test_open_file_with_gp3bit_set_created_by_osx_archive ::Zip::InputStream.open('test/data/osx-archive.zip') do |zis| - assert_raises(::Zip::GPFBit3Error) do + error = assert_raises(::Zip::StreamingError) do zis.get_next_entry end + assert_match(/1\.txt/, error.message) + assert_equal('1.txt', error.entry.name) end end From 19fe79e31e336ffc92771149cbeb84c2cb646039 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 14 Aug 2022 11:30:43 +0100 Subject: [PATCH 413/469] Define the SplitArchiveError message within the error class. --- lib/zip/entry.rb | 3 +-- lib/zip/errors.rb | 7 ++++++- test/input_stream_test.rb | 3 ++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index bfbf4b8b..879ae54a 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -326,8 +326,7 @@ def read_local_entry(io) #:nodoc:all unless @header_signature == LOCAL_ENTRY_SIGNATURE if @header_signature == SPLIT_FILE_SIGNATURE - raise SplitArchiveError, - 'Rubyzip cannot extract from split archives at this time' + raise SplitArchiveError end raise Error, "Zip local header magic not found at location '#{local_header_offset}'" diff --git a/lib/zip/errors.rb b/lib/zip/errors.rb index fd10022d..e8582b72 100644 --- a/lib/zip/errors.rb +++ b/lib/zip/errors.rb @@ -8,7 +8,6 @@ class EntryNameError < Error; end class EntrySizeError < Error; end class InternalError < Error; end class DecompressionError < Error; end - class SplitArchiveError < Error; end class CompressionMethodError < Error attr_reader :compression_method @@ -23,6 +22,12 @@ def message end end + class SplitArchiveError < Error + def message + 'Rubyzip cannot extract from split archives at this time.' + end + end + class StreamingError < Error attr_reader :entry diff --git a/test/input_stream_test.rb b/test/input_stream_test.rb index becb9336..e1b92073 100644 --- a/test/input_stream_test.rb +++ b/test/input_stream_test.rb @@ -89,9 +89,10 @@ def test_open_file_with_gp3bit_set_created_by_osx_archive def test_open_split_archive_raises_error ::Zip::InputStream.open('test/data/invalid-split.zip') do |zis| - assert_raises(::Zip::SplitArchiveError) do + error = assert_raises(::Zip::SplitArchiveError) do zis.get_next_entry end + refute(error.message.empty?) end end From 51231673a40742f6ad64fbde880a79cb5952ad62 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 14 Aug 2022 16:31:03 +0100 Subject: [PATCH 414/469] Define the DecompressionError message within the error class. --- lib/zip/errors.rb | 14 +++++++++++++- lib/zip/inflater.rb | 3 +-- test/encryption_test.rb | 3 ++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/zip/errors.rb b/lib/zip/errors.rb index e8582b72..d67c8c8e 100644 --- a/lib/zip/errors.rb +++ b/lib/zip/errors.rb @@ -7,7 +7,6 @@ class DestinationFileExistsError < Error; end class EntryNameError < Error; end class EntrySizeError < Error; end class InternalError < Error; end - class DecompressionError < Error; end class CompressionMethodError < Error attr_reader :compression_method @@ -22,6 +21,19 @@ def message end end + class DecompressionError < Error + attr_reader :zlib_error + + def initialize(zlib_error) + super() + @zlib_error = zlib_error + end + + def message + "Zlib error ('#{@zlib_error.message}') while inflating." + end + end + class SplitArchiveError < Error def message 'Rubyzip cannot extract from split archives at this time.' diff --git a/lib/zip/inflater.rb b/lib/zip/inflater.rb index c702da75..181603da 100644 --- a/lib/zip/inflater.rb +++ b/lib/zip/inflater.rb @@ -40,8 +40,7 @@ def produce_input retry end rescue Zlib::Error => e - raise ::Zip::DecompressionError, - "Zlib error ('#{e.message}') while inflating" + raise ::Zip::DecompressionError, e end def input_finished? diff --git a/test/encryption_test.rb b/test/encryption_test.rb index bed3fe68..6fd63950 100644 --- a/test/encryption_test.rb +++ b/test/encryption_test.rb @@ -38,7 +38,7 @@ def test_encrypt assert_equal content, zis.read end - assert_raises(Zip::DecompressionError) do + error = assert_raises(Zip::DecompressionError) do Zip::InputStream.open( encrypted_zip, decrypter: Zip::TraditionalDecrypter.new("#{password}wrong") @@ -47,6 +47,7 @@ def test_encrypt assert_equal content, zis.read end end + assert_match(/Zlib error \('.+'\) while inflating\./, error.message) end def test_decrypt From 04cc10a80fe788609d84aa0db48866a8c2a99b58 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 14 Aug 2022 16:33:13 +0100 Subject: [PATCH 415/469] Remove the InternalError class (never used). --- lib/zip/errors.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/zip/errors.rb b/lib/zip/errors.rb index d67c8c8e..bb15b6b5 100644 --- a/lib/zip/errors.rb +++ b/lib/zip/errors.rb @@ -6,7 +6,6 @@ class EntryExistsError < Error; end class DestinationFileExistsError < Error; end class EntryNameError < Error; end class EntrySizeError < Error; end - class InternalError < Error; end class CompressionMethodError < Error attr_reader :compression_method From 7097492dc8c6975cedc303d0d66c8f264f71a9e7 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 14 Aug 2022 22:05:42 +0100 Subject: [PATCH 416/469] Define the EntryExistsError message within the error class. --- lib/zip/errors.rb | 13 ++++++++++++- lib/zip/file.rb | 11 ++++------- test/case_sensitivity_test.rb | 3 ++- test/file_test.rb | 11 ++++++++--- test/settings_test.rb | 3 ++- 5 files changed, 28 insertions(+), 13 deletions(-) diff --git a/lib/zip/errors.rb b/lib/zip/errors.rb index bb15b6b5..d617645e 100644 --- a/lib/zip/errors.rb +++ b/lib/zip/errors.rb @@ -2,7 +2,6 @@ module Zip class Error < StandardError; end - class EntryExistsError < Error; end class DestinationFileExistsError < Error; end class EntryNameError < Error; end class EntrySizeError < Error; end @@ -33,6 +32,18 @@ def message end end + class EntryExistsError < Error + def initialize(source, name) + super() + @source = source + @name = name + end + + def message + "'#{@source}' failed. Entry #{@name} already exists." + end + end + class SplitArchiveError < Error def message 'Rubyzip cannot extract from split archives at this time.' diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 89a098f9..04e1d802 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -362,15 +362,12 @@ def initialize_cdir(path_or_io, buffer: false) end def check_entry_exists(entry_name, continue_on_exists_proc, proc_name) - continue_on_exists_proc ||= proc { Zip.continue_on_exists_proc } return unless @cdir.include?(entry_name) - if continue_on_exists_proc.call - remove get_entry(entry_name) - else - raise ::Zip::EntryExistsError, - proc_name + " failed. Entry #{entry_name} already exists" - end + continue_on_exists_proc ||= proc { Zip.continue_on_exists_proc } + raise ::Zip::EntryExistsError.new proc_name, entry_name unless continue_on_exists_proc.call + + remove get_entry(entry_name) end def check_file(path) diff --git a/test/case_sensitivity_test.rb b/test/case_sensitivity_test.rb index 81ad2072..07e84579 100644 --- a/test/case_sensitivity_test.rb +++ b/test/case_sensitivity_test.rb @@ -40,9 +40,10 @@ def test_add_case_insensitive SRC_FILES.each { |fn, _en| assert(::File.exist?(fn)) } zf = ::Zip::File.new(EMPTY_FILENAME, create: true) - assert_raises Zip::EntryExistsError do + error = assert_raises Zip::EntryExistsError do SRC_FILES.each { |fn, en| zf.add(en, fn) } end + assert_match(/'add'/, error.message) end # Ensure that names are treated case insensitively when reading files and +case_insensitive_match = true+ diff --git a/test/file_test.rb b/test/file_test.rb index dfc48c81..225a1ab4 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -389,11 +389,12 @@ def test_recover_permissions_after_add_files_to_archive end def test_add_existing_entry_name - assert_raises(::Zip::EntryExistsError) do + error = assert_raises(::Zip::EntryExistsError) do ::Zip::File.open(TEST_ZIP.zip_name) do |zf| zf.add(zf.entries.first.name, 'test/data/file2.txt') end end + assert_match(/'add'/, error.message) end def test_add_existing_entry_name_replace @@ -538,11 +539,12 @@ def test_rename_to_existing_entry old_entries = nil ::Zip::File.open(TEST_ZIP.zip_name) { |zf| old_entries = zf.entries } - assert_raises(::Zip::EntryExistsError) do + error = assert_raises(::Zip::EntryExistsError) do ::Zip::File.open(TEST_ZIP.zip_name) do |zf| zf.rename(zf.entries[0], zf.entries[1].name) end end + assert_match(/'rename'/, error.message) ::Zip::File.open(TEST_ZIP.zip_name) do |zf| assert_equal(old_entries.sort.map(&:name), zf.entries.sort.map(&:name)) @@ -586,7 +588,10 @@ def test_rename_non_entry def test_rename_entry_to_existing_entry entry1, entry2, * = TEST_ZIP.entry_names zf = ::Zip::File.new(TEST_ZIP.zip_name) - assert_raises(::Zip::EntryExistsError) { zf.rename(entry1, entry2) } + error = assert_raises(::Zip::EntryExistsError) do + zf.rename(entry1, entry2) + end + assert_match(/'rename'/, error.message) ensure zf.close end diff --git a/test/settings_test.rb b/test/settings_test.rb index a0c6906d..9fb50f1e 100644 --- a/test/settings_test.rb +++ b/test/settings_test.rb @@ -46,11 +46,12 @@ def test_false_on_exists_proc def test_false_continue_on_exists_proc Zip.continue_on_exists_proc = false - assert_raises(::Zip::EntryExistsError) do + error = assert_raises(::Zip::EntryExistsError) do ::Zip::File.open(TEST_ZIP.zip_name) do |zf| zf.add(zf.entries.first.name, 'test/data/file2.txt') end end + assert_match(/'add'/, error.message) end def test_true_continue_on_exists_proc From 07eca2bae89a7f4d79d6c7bcdb8d28e4185c1fdb Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 15 Aug 2022 22:02:33 +0100 Subject: [PATCH 417/469] Define the EntrySizeError message within the error class. --- lib/zip/entry.rb | 6 +++--- lib/zip/errors.rb | 14 +++++++++++++- test/file_extract_test.rb | 5 +++-- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 879ae54a..4d900171 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -711,10 +711,10 @@ def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exi bytes_written += buf.bytesize next unless bytes_written > size && !warned - message = "entry '#{name}' should be #{size}B, but is larger when inflated." - raise ::Zip::EntrySizeError, message if ::Zip.validate_entry_sizes + error = ::Zip::EntrySizeError.new(self) + raise error if ::Zip.validate_entry_sizes - warn "WARNING: #{message}" + warn "WARNING: #{error.message}" warned = true end end diff --git a/lib/zip/errors.rb b/lib/zip/errors.rb index d617645e..687a03de 100644 --- a/lib/zip/errors.rb +++ b/lib/zip/errors.rb @@ -4,7 +4,6 @@ module Zip class Error < StandardError; end class DestinationFileExistsError < Error; end class EntryNameError < Error; end - class EntrySizeError < Error; end class CompressionMethodError < Error attr_reader :compression_method @@ -44,6 +43,19 @@ def message end end + class EntrySizeError < Error + attr_reader :entry + + def initialize(entry) + super() + @entry = entry + end + + def message + "Entry '#{@entry.name}' should be #{@entry.size}B, but is larger when inflated." + end + end + class SplitArchiveError < Error def message 'Rubyzip cannot extract from split archives at this time.' diff --git a/test/file_extract_test.rb b/test/file_extract_test.rb index e5243cab..734351a8 100644 --- a/test/file_extract_test.rb +++ b/test/file_extract_test.rb @@ -139,9 +139,10 @@ def test_extract_incorrect_size error = assert_raises ::Zip::EntrySizeError do a_entry.extract end - assert_equal \ - "entry 'a' should be 1B, but is larger when inflated.", + assert_equal( + "Entry 'a' should be 1B, but is larger when inflated.", error.message + ) end end end From e3f0aecf93cd66a6c266c2e70b0183a286414530 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 16 Aug 2022 10:52:18 +0100 Subject: [PATCH 418/469] Define the EntryNameError message within the error class. --- lib/zip/entry.rb | 13 ++----------- lib/zip/errors.rb | 16 +++++++++++++++- test/entry_test.rb | 8 ++++++-- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 4d900171..a563aa4d 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -61,17 +61,8 @@ def set_default_vars_values end def check_name(name) - error = - if name.start_with?('/') - "Illegal entry name '#{name}'. Names must not start with '/'" - elsif name.length > 65_535 - 'Illegal entry name. Names must have fewer than 65,536 characters' - else - '' - end - return if error.empty? - - raise EntryNameError, error + raise EntryNameError, name if name.start_with?('/') + raise EntryNameError if name.length > 65_535 end def initialize( diff --git a/lib/zip/errors.rb b/lib/zip/errors.rb index 687a03de..feac3b84 100644 --- a/lib/zip/errors.rb +++ b/lib/zip/errors.rb @@ -3,7 +3,6 @@ module Zip class Error < StandardError; end class DestinationFileExistsError < Error; end - class EntryNameError < Error; end class CompressionMethodError < Error attr_reader :compression_method @@ -43,6 +42,21 @@ def message end end + class EntryNameError < Error + def initialize(name = nil) + super() + @name = name + end + + def message + if @name.nil? + 'Illegal entry name. Names must have fewer than 65,536 characters.' + else + "Illegal entry name '#{@name}'. Names must not start with '/'." + end + end + end + class EntrySizeError < Error attr_reader :entry diff --git a/test/entry_test.rb b/test/entry_test.rb index 0ecf2f4a..0d88c874 100644 --- a/test/entry_test.rb +++ b/test/entry_test.rb @@ -150,16 +150,20 @@ def test_parent_as_string end def test_entry_name_cannot_start_with_slash - assert_raises(::Zip::EntryNameError) { ::Zip::Entry.new('zf.zip', '/hej/der') } + error = assert_raises(::Zip::EntryNameError) do + ::Zip::Entry.new('zf.zip', '/hej/der') + end + assert_match(/'\/hej\/der'/, error.message) end def test_entry_name_cannot_be_too_long name = 'a' * 65_535 ::Zip::Entry.new('', name) # Should not raise anything. - assert_raises(::Zip::EntryNameError) do + error = assert_raises(::Zip::EntryNameError) do ::Zip::Entry.new('', "a#{name}") end + assert_match(/65,536/, error.message) end def test_store_file_without_compression From 750d3723806a3bf8f8f9bd69cb98dfd1133493eb Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 16 Aug 2022 11:13:30 +0100 Subject: [PATCH 419/469] Rename DestinationFileExistsError -> DestinationExistsError. And define the error message within the class. --- lib/zip/entry.rb | 15 ++++++--------- lib/zip/errors.rb | 13 ++++++++++++- test/file_extract_directory_test.rb | 4 +++- test/file_extract_test.rb | 3 ++- test/settings_test.rb | 4 +++- 5 files changed, 26 insertions(+), 13 deletions(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index a563aa4d..7c7f2e17 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -689,9 +689,9 @@ def set_time(binary_dos_date, binary_dos_time) def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exists_proc }) if ::File.exist?(dest_path) && !yield(self, dest_path) - raise ::Zip::DestinationFileExistsError, - "Destination '#{dest_path}' already exists" + raise ::Zip::DestinationExistsError, dest_path end + ::File.open(dest_path, 'wb') do |os| get_input_stream do |is| bytes_written = 0 @@ -718,14 +718,11 @@ def create_directory(dest_path) return if ::File.directory?(dest_path) if ::File.exist?(dest_path) - if block_given? && yield(self, dest_path) - ::FileUtils.rm_f dest_path - else - raise ::Zip::DestinationFileExistsError, - "Cannot create directory '#{dest_path}'. " \ - 'A file already exists with that name' - end + raise ::Zip::DestinationExistsError, dest_path unless block_given? && yield(self, dest_path) + + ::FileUtils.rm_f dest_path end + ::FileUtils.mkdir_p(dest_path) set_extra_attributes_on_path(dest_path) end diff --git a/lib/zip/errors.rb b/lib/zip/errors.rb index feac3b84..74bf3a3d 100644 --- a/lib/zip/errors.rb +++ b/lib/zip/errors.rb @@ -2,7 +2,6 @@ module Zip class Error < StandardError; end - class DestinationFileExistsError < Error; end class CompressionMethodError < Error attr_reader :compression_method @@ -30,6 +29,18 @@ def message end end + class DestinationExistsError < Error + def initialize(destination) + super() + @destination = destination + end + + def message + "Cannot create file or directory '#{@destination}'. " \ + 'A file already exists with that name.' + end + end + class EntryExistsError < Error def initialize(source, name) super() diff --git a/test/file_extract_directory_test.rb b/test/file_extract_directory_test.rb index fc10979d..26730a08 100644 --- a/test/file_extract_directory_test.rb +++ b/test/file_extract_directory_test.rb @@ -38,7 +38,9 @@ def test_extract_directory_exists_as_dir def test_extract_directory_exists_as_file File.open(TEST_OUT_NAME, 'w') { |f| f.puts 'something' } - assert_raises(::Zip::DestinationFileExistsError) { extract_test_dir } + assert_raises(::Zip::DestinationExistsError) do + extract_test_dir + end end def test_extract_directory_exists_as_file_overwrite diff --git a/test/file_extract_test.rb b/test/file_extract_test.rb index 734351a8..0bce8e1b 100644 --- a/test/file_extract_test.rb +++ b/test/file_extract_test.rb @@ -39,11 +39,12 @@ def test_extract_exists text = 'written text' ::File.open(EXTRACTED_FILENAME, 'w') { |f| f.write(text) } - assert_raises(::Zip::DestinationFileExistsError) do + assert_raises(::Zip::DestinationExistsError) do ::Zip::File.open(TEST_ZIP.zip_name) do |zf| zf.extract(zf.entries.first, EXTRACTED_FILENAME) end end + File.open(EXTRACTED_FILENAME, 'r') do |f| assert_equal(text, f.read) end diff --git a/test/settings_test.rb b/test/settings_test.rb index 9fb50f1e..3bf66e4e 100644 --- a/test/settings_test.rb +++ b/test/settings_test.rb @@ -40,7 +40,9 @@ def test_true_on_exists_proc def test_false_on_exists_proc Zip.on_exists_proc = false File.open(TEST_OUT_NAME, 'w') { |f| f.puts 'something' } - assert_raises(Zip::DestinationFileExistsError) { extract_test_dir } + assert_raises(Zip::DestinationExistsError) do + extract_test_dir + end end def test_false_continue_on_exists_proc From d6eb73566c2a5aed030d0c1dbfe3cb930e4c1de8 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 31 Jan 2022 20:43:55 -0800 Subject: [PATCH 420/469] Enable Zip64 by default Previously if RubyZip attempted to create an archive with more than 64K entries, the central directory would truncate the count. `unzip` and `zipinfo` would fail with an error message such as: ``` error: expected central file header signature not found (file #93272). (please check that you have transferred or created the zipfile in the appropriate BINARY mode and that you have compiled UnZip properly) ``` This generated a lot of confusion and a production issue since many tools fail to decode a RubyZip-created archive if Zip64 is not enabled for a large number of files. Since Zip64 support is now the norm, enable this by default. --- README.md | 4 ++-- lib/zip.rb | 2 +- test/local_entry_test.rb | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2dca73c5..232e7cb4 100644 --- a/README.md +++ b/README.md @@ -336,10 +336,10 @@ end ### Zip64 Support -By default, Zip64 support is disabled for writing. To enable it do this: +By default, Zip64 support is enabled for writing. To disable it do this: ```ruby -Zip.write_zip64_support = true +Zip.write_zip64_support = false ``` _NOTE_: If you will enable Zip64 writing then you will need zip extractor with Zip64 support to extract archive. diff --git a/lib/zip.rb b/lib/zip.rb index 87a236a8..82c52003 100644 --- a/lib/zip.rb +++ b/lib/zip.rb @@ -61,7 +61,7 @@ def reset! @continue_on_exists_proc = false @sort_entries = false @default_compression = ::Zlib::DEFAULT_COMPRESSION - @write_zip64_support = false + @write_zip64_support = true @warn_invalid_date = true @case_insensitive_match = false @force_entry_names_encoding = nil diff --git a/test/local_entry_test.rb b/test/local_entry_test.rb index f934d49d..a6a02f32 100644 --- a/test/local_entry_test.rb +++ b/test/local_entry_test.rb @@ -62,6 +62,7 @@ def test_read_local_entry_from_truncated_zip_file_returns_nil end def test_write_entry + ::Zip.write_zip64_support = false entry = ::Zip::Entry.new( 'file.zip', 'entry_name', comment: 'my little comment', size: 400, extra: 'thisIsSomeExtraInformation', compressed_size: 100, crc: 987_654 From f460da3afb0142f299b2987bbfc25a2ad5aa6724 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Fri, 11 Nov 2022 17:51:17 +0000 Subject: [PATCH 421/469] Prevent unnecessary Zip64 data being stored. With Zip64 write support enabled by default, it's important that we only store the extra data when we need to. This commit ensures that the Zip64 extra data is included for an entry if its size is over 4GB, or if we don't know how big it will be at the point of writing the local header data. This commit also removes the need for the Zip64Placeholder extra data field. Now we just use the Zip64 field itself and ensure it's filled in correctly. --- README.md | 2 +- lib/zip/entry.rb | 75 +++++++++++++----------- lib/zip/extra_field.rb | 1 - lib/zip/extra_field/zip64_placeholder.rb | 17 ------ test/central_directory_test.rb | 1 - test/file_extract_test.rb | 66 +++++++++++++++++++++ test/file_test.rb | 14 +++-- test/local_entry_test.rb | 44 +++++++++----- test/zip64_full_test.rb | 1 - 9 files changed, 146 insertions(+), 75 deletions(-) delete mode 100644 lib/zip/extra_field/zip64_placeholder.rb diff --git a/README.md b/README.md index 232e7cb4..3093e6b8 100644 --- a/README.md +++ b/README.md @@ -342,7 +342,7 @@ By default, Zip64 support is enabled for writing. To disable it do this: Zip.write_zip64_support = false ``` -_NOTE_: If you will enable Zip64 writing then you will need zip extractor with Zip64 support to extract archive. +_NOTE_: If Zip64 write support is enabled then any extractor subsequently used may also require Zip64 support to read from the resultant archive. ### Block Form diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 7c7f2e17..36bc32c9 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -21,13 +21,15 @@ class Entry attr_accessor :comment, :compressed_size, :follow_symlinks, :name, :restore_ownership, :restore_permissions, :restore_times, - :size, :unix_gid, :unix_perms, :unix_uid + :unix_gid, :unix_perms, :unix_uid attr_accessor :crc, :external_file_attributes, :fstype, :gp_flags, :internal_file_attributes, :local_header_offset # :nodoc: attr_reader :extra, :compression_level, :filepath # :nodoc: + attr_writer :size # :nodoc: + mark_dirty :comment=, :compressed_size=, :external_file_attributes=, :fstype=, :gp_flags=, :name=, :size=, :unix_gid=, :unix_perms=, :unix_uid= @@ -67,7 +69,7 @@ def check_name(name) def initialize( zipfile = '', name = '', - comment: '', size: 0, compressed_size: 0, crc: 0, + comment: '', size: nil, compressed_size: 0, crc: 0, compression_method: DEFLATED, compression_level: ::Zip.default_compression, time: ::Zip::DOSTime.now, extra: ::Zip::ExtraField.new @@ -85,7 +87,7 @@ def initialize( @compression_level = compression_level || ::Zip.default_compression @compressed_size = compressed_size || 0 @crc = crc || 0 - @size = size || 0 + @size = size @time = case time when ::Zip::DOSTime time @@ -108,6 +110,10 @@ def incomplete? gp_flags & 8 == 8 end + def size + @size || 0 + end + def time(component: :mtime) time = if @extra['UniversalTime'] @@ -355,13 +361,13 @@ def pack_local_entry @time.to_binary_dos_date, # @last_mod_date @crc, zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size, - zip64 && zip64.original_size ? 0xFFFFFFFF : @size, + zip64 && zip64.original_size ? 0xFFFFFFFF : (@size || 0), name_size, @extra ? @extra.local_size : 0].pack('VvvvvvVVVvv') end def write_local_entry(io, rewrite: false) #:nodoc:all - prep_zip64_extra(true) + prep_local_zip64_extra verify_local_header_size! if rewrite @local_header_offset = io.tell @@ -531,7 +537,7 @@ def pack_c_dir_entry @time.to_binary_dos_date, # @last_mod_date @crc, zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size, - zip64 && zip64.original_size ? 0xFFFFFFFF : @size, + zip64 && zip64.original_size ? 0xFFFFFFFF : (@size || 0), name_size, @extra ? @extra.c_dir_size : 0, comment_size, @@ -546,7 +552,8 @@ def pack_c_dir_entry end def write_c_dir_entry(io) #:nodoc:all - prep_zip64_extra(false) + prep_cdir_zip64_extra + case @fstype when ::Zip::FSTYPE_UNIX ft = case ftype @@ -645,6 +652,7 @@ def gather_fileinfo_from_srcpath(src_path) # :nodoc: end @filepath = src_path + @size = stat.size get_extra_attributes_from_path(@filepath) end @@ -769,39 +777,38 @@ def set_compression_level_flags end end - # create a zip64 extra information field if we need one - def prep_zip64_extra(for_local_header) #:nodoc:all + # rubocop:disable Style/GuardClause + def prep_local_zip64_extra return unless ::Zip.write_zip64_support + return if (!zip64? && @size && @size < 0xFFFFFFFF) || !file? - need_zip64 = @size >= 0xFFFFFFFF || @compressed_size >= 0xFFFFFFFF - need_zip64 ||= @local_header_offset >= 0xFFFFFFFF unless for_local_header - if need_zip64 + # Might not know size here, so need ZIP64 just in case. + # If we already have a ZIP64 extra (placeholder) then we must fill it in. + if zip64? || @size.nil? || @size >= 0xFFFFFFFF || @compressed_size >= 0xFFFFFFFF @version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT_ZIP64 - @extra.delete('Zip64Placeholder') - zip64 = @extra.create('Zip64') - if for_local_header - # local header always includes size and compressed size - zip64.original_size = @size - zip64.compressed_size = @compressed_size - else - # central directory entry entries include whichever fields are necessary - zip64.original_size = @size if @size >= 0xFFFFFFFF - zip64.compressed_size = @compressed_size if @compressed_size >= 0xFFFFFFFF - zip64.relative_header_offset = @local_header_offset if @local_header_offset >= 0xFFFFFFFF - end - else - @extra.delete('Zip64') + zip64 = @extra['Zip64'] || @extra.create('Zip64') - # if this is a local header entry, create a placeholder - # so we have room to write a zip64 extra field afterward - # (we won't know if it's needed until the file data is written) - if for_local_header - @extra.create('Zip64Placeholder') - else - @extra.delete('Zip64Placeholder') - end + # Local header always includes size and compressed size. + zip64.original_size = @size || 0 + zip64.compressed_size = @compressed_size end end + + def prep_cdir_zip64_extra + return unless ::Zip.write_zip64_support + + if (@size && @size >= 0xFFFFFFFF) || @compressed_size >= 0xFFFFFFFF || + @local_header_offset >= 0xFFFFFFFF + @version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT_ZIP64 + zip64 = @extra['Zip64'] || @extra.create('Zip64') + + # Central directory entry entries include whichever fields are necessary. + zip64.original_size = @size if @size && @size >= 0xFFFFFFFF + zip64.compressed_size = @compressed_size if @compressed_size >= 0xFFFFFFFF + zip64.relative_header_offset = @local_header_offset if @local_header_offset >= 0xFFFFFFFF + end + end + # rubocop:enable Style/GuardClause end end diff --git a/lib/zip/extra_field.rb b/lib/zip/extra_field.rb index 658675b4..8a73c3c9 100644 --- a/lib/zip/extra_field.rb +++ b/lib/zip/extra_field.rb @@ -90,7 +90,6 @@ def local_size require 'zip/extra_field/old_unix' require 'zip/extra_field/unix' require 'zip/extra_field/zip64' -require 'zip/extra_field/zip64_placeholder' require 'zip/extra_field/ntfs' # Copyright (C) 2002, 2003 Thomas Sondergaard diff --git a/lib/zip/extra_field/zip64_placeholder.rb b/lib/zip/extra_field/zip64_placeholder.rb deleted file mode 100644 index 2619a68e..00000000 --- a/lib/zip/extra_field/zip64_placeholder.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -module Zip - # placeholder to reserve space for a Zip64 extra information record, for the - # local file header only, that we won't know if we'll need until after - # we write the file data - class ExtraField::Zip64Placeholder < ExtraField::Generic - HEADER_ID = ['9999'].pack('H*') # this ID is used by other libraries such as .NET's Ionic.zip - register_map - - def initialize(_binstr = nil); end - - def pack_for_local - "\x00" * 16 - end - end -end diff --git a/test/central_directory_test.rb b/test/central_directory_test.rb index 6197eb3b..37b5ca65 100644 --- a/test/central_directory_test.rb +++ b/test/central_directory_test.rb @@ -88,7 +88,6 @@ def test_write_to_stream end def test_write64_to_stream - ::Zip.write_zip64_support = true entries = [ ::Zip::Entry.new( 'file.zip', 'file1-little', comment: 'comment1', size: 200, diff --git a/test/file_extract_test.rb b/test/file_extract_test.rb index 0bce8e1b..3b4cbb97 100644 --- a/test/file_extract_test.rb +++ b/test/file_extract_test.rb @@ -89,6 +89,8 @@ def test_extract_another_non_entry end def test_extract_incorrect_size + Zip.write_zip64_support = false + # The uncompressed size fields in the zip file cannot be trusted. This makes # it harder for callers to validate the sizes of the files they are # extracting, which can lead to denial of service. See also @@ -148,4 +150,68 @@ def test_extract_incorrect_size end end end + + def test_extract_incorrect_size_zip64 + # The uncompressed size fields in the zip file cannot be trusted. This makes + # it harder for callers to validate the sizes of the files they are + # extracting, which can lead to denial of service. See also + # https://en.wikipedia.org/wiki/Zip_bomb + # + # This version of the test ensures that fraudulent sizes in the ZIP64 + # extensions are caught. + Dir.mktmpdir do |tmp| + real_zip = File.join(tmp, 'real.zip') + fake_zip = File.join(tmp, 'fake.zip') + file_name = 'a' + true_size = 500_000 + fake_size = 1 + + ::Zip::File.open(real_zip, create: true) do |zf| + zf.get_output_stream(file_name) do |os| + os.write 'a' * true_size + end + end + + compressed_size = nil + ::Zip::File.open(real_zip) do |zf| + a_entry = zf.find_entry(file_name) + compressed_size = a_entry.compressed_size + assert_equal true_size, a_entry.size + end + + true_size_bytes = [0x1, 16, true_size, compressed_size].pack('vvQ Date: Thu, 17 Nov 2022 18:02:32 +0000 Subject: [PATCH 422/469] Only use the Zip64 CDIR end locator if needed. Previously the central directory Zip64 data was written even if it wasn't strictly needed. The standard allows for entries to include Zip64 data (say, if they are streamed and their size is unknown when writing the file data) without needing any Zip64 data in the central directory. So now we only write central directory Zip64 data if there are over 65535 files or the file data is huge. --- lib/zip/central_directory.rb | 12 ++++-------- test/central_directory_test.rb | 35 +++++++++------------------------- 2 files changed, 13 insertions(+), 34 deletions(-) diff --git a/lib/zip/central_directory.rb b/lib/zip/central_directory.rb index 146c444f..62de599b 100644 --- a/lib/zip/central_directory.rb +++ b/lib/zip/central_directory.rb @@ -44,14 +44,10 @@ def write_to_stream(io) #:nodoc: @entry_set.each { |entry| entry.write_c_dir_entry(io) } eocd_offset = io.tell cdir_size = eocd_offset - cdir_offset - if ::Zip.write_zip64_support - need_zip64_eocd = cdir_offset > 0xFFFFFFFF || cdir_size > 0xFFFFFFFF \ - || @entry_set.size > 0xFFFF - need_zip64_eocd ||= @entry_set.any?(&:zip64?) - if need_zip64_eocd - write_64_e_o_c_d(io, cdir_offset, cdir_size) - write_64_eocd_locator(io, eocd_offset) - end + if Zip.write_zip64_support && + (cdir_offset > 0xFFFFFFFF || cdir_size > 0xFFFFFFFF || @entry_set.size > 0xFFFF) + write_64_e_o_c_d(io, cdir_offset, cdir_size) + write_64_eocd_locator(io, eocd_offset) end write_e_o_c_d(io, cdir_offset, cdir_size) end diff --git a/test/central_directory_test.rb b/test/central_directory_test.rb index 37b5ca65..c36d2dc4 100644 --- a/test/central_directory_test.rb +++ b/test/central_directory_test.rb @@ -87,43 +87,26 @@ def test_write_to_stream assert_equal(cdir.entries.sort, cdir_readback.entries.sort) end - def test_write64_to_stream - entries = [ - ::Zip::Entry.new( - 'file.zip', 'file1-little', comment: 'comment1', size: 200, - compressed_size: 200, crc: 101, - compression_method: ::Zip::Entry::STORED - ), - ::Zip::Entry.new( - 'file.zip', 'file2-big', comment: 'comment2', - size: 20_000_000_000, compressed_size: 18_000_000_000, crc: 102 - ), - ::Zip::Entry.new( - 'file.zip', 'file3-alsobig', comment: 'comment3', - size: 21_000_000_000, compressed_size: 15_000_000_000, crc: 103 - ), - ::Zip::Entry.new( - 'file.zip', 'file4-little', comment: 'comment4', - size: 121, compressed_size: 100, crc: 104 - ) - ] + def test_write64_to_stream_65536_entries + skip unless ENV['FULL_ZIP64_TEST'] - [0, 250, 18_000_000_300, 33_000_000_350].each_with_index do |offset, index| - entries[index].local_header_offset = offset + entries = [] + 0x10000.times do |i| + entries << Zip::Entry.new('file.zip', "#{i}.txt") end - cdir = ::Zip::CentralDirectory.new(entries, 'zip comment') + cdir = Zip::CentralDirectory.new(entries) File.open('test/data/generated/cdir64test.bin', 'wb') do |f| cdir.write_to_stream(f) end - cdir_readback = ::Zip::CentralDirectory.new + cdir_readback = Zip::CentralDirectory.new File.open('test/data/generated/cdir64test.bin', 'rb') do |f| cdir_readback.read_from_stream(f) end - assert_equal(cdir.entries.sort, cdir_readback.entries.sort) - assert_equal(::Zip::VERSION_NEEDED_TO_EXTRACT_ZIP64, cdir_readback.instance_variable_get(:@version_needed_for_extract)) + assert_equal(0x10000, cdir_readback.size) + assert_equal(Zip::VERSION_NEEDED_TO_EXTRACT_ZIP64, cdir_readback.instance_variable_get(:@version_needed_for_extract)) end def test_equality From 84087e57740c32ce24184cb91e4e0d443ebfd242 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 3 Jan 2023 15:37:58 +0000 Subject: [PATCH 423/469] Ensure that entries can be extracted safely without path traversal. This commit adds a parameter to the `File#extract` and `Entry#extract` methods so that a base destination directory can be specified for extracting archives in bulk to somewhere in the filesystem that isn't the current working directory. This directory is `.` by default. It is combined with the entry path - which shouldn't but could have relative directories (e.g. `..`) in it - and tested for safety before extracting. Resolves #540. --- lib/zip/entry.rb | 20 +++++---- lib/zip/file.rb | 11 +++-- test/file_extract_directory_test.rb | 2 +- test/file_extract_test.rb | 3 +- test/file_options_test.rb | 64 ++++++++++++++------------- test/path_traversal_test.rb | 68 ++++++++++++++++++++--------- 6 files changed, 103 insertions(+), 65 deletions(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 36bc32c9..9c42e079 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -247,21 +247,25 @@ def next_header_offset #:nodoc:all local_entry_offset + compressed_size end - # Extracts entry to file dest_path (defaults to @name). - # NB: The caller is responsible for making sure dest_path is safe, if it - # is passed. - def extract(dest_path = nil, &block) - if dest_path.nil? && !name_safe? - warn "WARNING: skipped '#{@name}' as unsafe." + # Extracts this entry to a file at `entry_path`, with + # `destination_directory` as the base location in the filesystem. + # + # NB: The caller is responsible for making sure `destination_directory` is + # safe, if it is passed. + def extract(entry_path = @name, destination_directory: '.', &block) + dest_dir = ::File.absolute_path(destination_directory || '.') + extract_path = ::File.absolute_path(::File.join(dest_dir, entry_path)) + + unless extract_path.start_with?(dest_dir) + warn "WARNING: skipped extracting '#{@name}' to '#{extract_path}' as unsafe." return self end - dest_path ||= @name block ||= proc { ::Zip.on_exists_proc } raise "unknown file type #{inspect}" unless directory? || file? || symlink? - __send__("create_#{ftype}", dest_path, &block) + __send__("create_#{ftype}", extract_path, &block) self end diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 04e1d802..69c73f5e 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -245,11 +245,16 @@ def replace(entry, src_path) add(entry, src_path) end - # Extracts entry to file dest_path. - def extract(entry, dest_path, &block) + # Extracts `entry` to a file at `entry_path`, with `destination_directory` + # as the base location in the filesystem. + # + # NB: The caller is responsible for making sure `destination_directory` is + # safe, if it is passed. + def extract(entry, entry_path = nil, destination_directory: '.', &block) block ||= proc { ::Zip.on_exists_proc } found_entry = get_entry(entry) - found_entry.extract(dest_path, &block) + entry_path ||= found_entry.name + found_entry.extract(entry_path, destination_directory: destination_directory, &block) end # Commits changes that has been made since the previous commit to diff --git a/test/file_extract_directory_test.rb b/test/file_extract_directory_test.rb index 26730a08..17d0995a 100644 --- a/test/file_extract_directory_test.rb +++ b/test/file_extract_directory_test.rb @@ -48,7 +48,7 @@ def test_extract_directory_exists_as_file_overwrite called = false extract_test_dir do |entry, dest_path| called = true - assert_equal(TEST_OUT_NAME, dest_path) + assert_equal(File.absolute_path(TEST_OUT_NAME), dest_path) assert(entry.directory?) true end diff --git a/test/file_extract_test.rb b/test/file_extract_test.rb index 3b4cbb97..77da4a6d 100644 --- a/test/file_extract_test.rb +++ b/test/file_extract_test.rb @@ -5,6 +5,7 @@ class ZipFileExtractTest < MiniTest::Test include CommonZipFileFixture EXTRACTED_FILENAME = 'test/data/generated/extEntry' + EXTRACTED_FILENAME_ABS = ::File.absolute_path(EXTRACTED_FILENAME) ENTRY_TO_EXTRACT, *REMAINING_ENTRIES = TEST_ZIP.entry_names.reverse def setup @@ -58,7 +59,7 @@ def test_extract_exists_overwrite ::Zip::File.open(TEST_ZIP.zip_name) do |zf| zf.extract(zf.entries.first, EXTRACTED_FILENAME) do |entry, extract_loc| called_correctly = zf.entries.first == entry && - extract_loc == EXTRACTED_FILENAME + extract_loc == EXTRACTED_FILENAME_ABS true end end diff --git a/test/file_options_test.rb b/test/file_options_test.rb index c1a568a6..c6b7885a 100644 --- a/test/file_options_test.rb +++ b/test/file_options_test.rb @@ -7,12 +7,15 @@ class FileOptionsTest < MiniTest::Test TXTPATH = ::File.expand_path(::File.join('data', 'file1.txt'), __dir__).freeze TXTPATH_600 = ::File.join(Dir.tmpdir, 'file1.600.txt').freeze TXTPATH_755 = ::File.join(Dir.tmpdir, 'file1.755.txt').freeze - EXTPATH_1 = ::File.join(Dir.tmpdir, 'extracted_1.txt').freeze - EXTPATH_2 = ::File.join(Dir.tmpdir, 'extracted_2.txt').freeze - EXTPATH_3 = ::File.join(Dir.tmpdir, 'extracted_3.txt').freeze ENTRY_1 = 'entry_1.txt' ENTRY_2 = 'entry_2.txt' ENTRY_3 = 'entry_3.txt' + EXTRACT_1 = 'extracted_1.txt' + EXTRACT_2 = 'extracted_2.txt' + EXTRACT_3 = 'extracted_3.txt' + EXTPATH_1 = ::File.join(Dir.tmpdir, EXTRACT_1).freeze + EXTPATH_2 = ::File.join(Dir.tmpdir, EXTRACT_2).freeze + EXTPATH_3 = ::File.join(Dir.tmpdir, EXTRACT_3).freeze def teardown ::File.unlink(ZIPPATH) if ::File.exist?(ZIPPATH) @@ -37,9 +40,9 @@ def test_restore_permissions_true end ::Zip::File.open(ZIPPATH, restore_permissions: true) do |zip| - zip.extract(ENTRY_1, EXTPATH_1) - zip.extract(ENTRY_2, EXTPATH_2) - zip.extract(ENTRY_3, EXTPATH_3) + zip.extract(ENTRY_1, EXTRACT_1, destination_directory: Dir.tmpdir) + zip.extract(ENTRY_2, EXTRACT_2, destination_directory: Dir.tmpdir) + zip.extract(ENTRY_3, EXTRACT_3, destination_directory: Dir.tmpdir) end assert_equal(::File.stat(TXTPATH).mode, ::File.stat(EXTPATH_1).mode) @@ -61,9 +64,9 @@ def test_restore_permissions_false end ::Zip::File.open(ZIPPATH, restore_permissions: false) do |zip| - zip.extract(ENTRY_1, EXTPATH_1) - zip.extract(ENTRY_2, EXTPATH_2) - zip.extract(ENTRY_3, EXTPATH_3) + zip.extract(ENTRY_1, EXTRACT_1, destination_directory: Dir.tmpdir) + zip.extract(ENTRY_2, EXTRACT_2, destination_directory: Dir.tmpdir) + zip.extract(ENTRY_3, EXTRACT_3, destination_directory: Dir.tmpdir) end default_perms = (Zip::RUNNING_ON_WINDOWS ? 0o100_644 : 0o100_666) - ::File.umask @@ -86,9 +89,9 @@ def test_restore_permissions_as_default end ::Zip::File.open(ZIPPATH) do |zip| - zip.extract(ENTRY_1, EXTPATH_1) - zip.extract(ENTRY_2, EXTPATH_2) - zip.extract(ENTRY_3, EXTPATH_3) + zip.extract(ENTRY_1, EXTRACT_1, destination_directory: Dir.tmpdir) + zip.extract(ENTRY_2, EXTRACT_2, destination_directory: Dir.tmpdir) + zip.extract(ENTRY_3, EXTRACT_3, destination_directory: Dir.tmpdir) end assert_equal(::File.stat(TXTPATH).mode, ::File.stat(EXTPATH_1).mode) @@ -103,8 +106,8 @@ def test_restore_times_true end ::Zip::File.open(ZIPPATH, restore_times: true) do |zip| - zip.extract(ENTRY_1, EXTPATH_1) - zip.extract(ENTRY_2, EXTPATH_2) + zip.extract(ENTRY_1, EXTRACT_1, destination_directory: Dir.tmpdir) + zip.extract(ENTRY_2, EXTRACT_2, destination_directory: Dir.tmpdir) end assert_time_equal(::File.mtime(TXTPATH), ::File.mtime(EXTPATH_1)) @@ -118,8 +121,8 @@ def test_restore_times_false end ::Zip::File.open(ZIPPATH, restore_times: false) do |zip| - zip.extract(ENTRY_1, EXTPATH_1) - zip.extract(ENTRY_2, EXTPATH_2) + zip.extract(ENTRY_1, EXTRACT_1, destination_directory: Dir.tmpdir) + zip.extract(ENTRY_2, EXTRACT_2, destination_directory: Dir.tmpdir) end assert_time_equal(::Time.now, ::File.mtime(EXTPATH_1)) @@ -133,8 +136,8 @@ def test_restore_times_true_as_default end ::Zip::File.open(ZIPPATH) do |zip| - zip.extract(ENTRY_1, EXTPATH_1) - zip.extract(ENTRY_2, EXTPATH_2) + zip.extract(ENTRY_1, EXTRACT_1, destination_directory: Dir.tmpdir) + zip.extract(ENTRY_2, EXTRACT_2, destination_directory: Dir.tmpdir) end assert_time_equal(::File.mtime(TXTPATH), ::File.mtime(EXTPATH_1)) @@ -143,20 +146,19 @@ def test_restore_times_true_as_default def test_get_find_consistency testzip = ::File.expand_path(::File.join('data', 'globTest.zip'), __dir__) - file_f = ::File.expand_path('f_test.txt', Dir.tmpdir) - file_g = ::File.expand_path('g_test.txt', Dir.tmpdir) - - ::Zip::File.open(testzip) do |zip| - e1 = zip.find_entry('globTest/food.txt') - e1.extract(file_f) - e2 = zip.get_entry('globTest/food.txt') - e2.extract(file_g) + Dir.mktmpdir do |tmp| + file_f = ::File.expand_path('f_test.txt', tmp) + file_g = ::File.expand_path('g_test.txt', tmp) + + ::Zip::File.open(testzip) do |zip| + e1 = zip.find_entry('globTest/food.txt') + e1.extract('f_test.txt', destination_directory: tmp) + e2 = zip.get_entry('globTest/food.txt') + e2.extract('g_test.txt', destination_directory: tmp) + end + + assert_time_equal(::File.mtime(file_f), ::File.mtime(file_g)) end - - assert_time_equal(::File.mtime(file_f), ::File.mtime(file_g)) - ensure - ::File.unlink(file_f) - ::File.unlink(file_g) end private diff --git a/test/path_traversal_test.rb b/test/path_traversal_test.rb index 4b500191..e1ec9a74 100644 --- a/test/path_traversal_test.rb +++ b/test/path_traversal_test.rb @@ -39,23 +39,31 @@ def in_tmpdir end def test_leading_slash - entries = { '/tmp/moo' => /WARNING: skipped '\/tmp\/moo'/ } - in_tmpdir do + entries = { '/tmp/moo' => '' } + in_tmpdir do |test_path| + Dir.mkdir('tmp') # Create 'tmp' dir within test directory. extract_paths(['jwilk', 'absolute1.zip'], entries) + + # Check that only the relative file is created. refute File.exist?('/tmp/moo') + assert File.exist?(File.join(test_path, 'tmp', 'moo')) end end def test_multiple_leading_slashes - entries = { '//tmp/moo' => /WARNING: skipped '\/\/tmp\/moo'/ } - in_tmpdir do + entries = { '//tmp/moo' => '' } + in_tmpdir do |test_path| + Dir.mkdir('tmp') # Create 'tmp' dir within test directory. extract_paths(['jwilk', 'absolute2.zip'], entries) + + # Check that only the relative file is created. refute File.exist?('/tmp/moo') + assert File.exist?(File.join(test_path, 'tmp', 'moo')) end end def test_leading_dot_dot - entries = { '../moo' => /WARNING: skipped '\.\.\/moo'/ } + entries = { '../moo' => /WARNING: skipped extracting '\.\.\/moo'/ } in_tmpdir do extract_paths(['jwilk', 'relative0.zip'], entries) refute File.exist?('../moo') @@ -65,7 +73,7 @@ def test_leading_dot_dot def test_non_leading_dot_dot_with_existing_folder entries = { 'tmp/' => '', - 'tmp/../../moo' => /WARNING: skipped 'tmp\/\.\.\/\.\.\/moo'/ + 'tmp/../../moo' => /WARNING: skipped extracting 'tmp\/\.\.\/\.\.\/moo'/ } in_tmpdir do extract_paths('relative1.zip', entries) @@ -75,7 +83,7 @@ def test_non_leading_dot_dot_with_existing_folder end def test_non_leading_dot_dot_without_existing_folder - entries = { 'tmp/../../moo' => /WARNING: skipped 'tmp\/\.\.\/\.\.\/moo'/ } + entries = { 'tmp/../../moo' => /WARNING: skipped extracting 'tmp\/\.\.\/\.\.\/moo'/ } in_tmpdir do extract_paths(['jwilk', 'relative2.zip'], entries) refute File.exist?('../moo') @@ -94,7 +102,7 @@ def test_file_symlink def test_directory_symlink # Can't create tmp/moo, because the tmp symlink is skipped. entries = { - 'tmp' => /WARNING: skipped symlink 'tmp'/, + 'tmp' => /WARNING: skipped symlink '.*\/tmp'/, 'tmp/moo' => :error } in_tmpdir do @@ -106,8 +114,8 @@ def test_directory_symlink def test_two_directory_symlinks_a # Can't create par/moo because the symlinks are skipped. entries = { - 'cur' => /WARNING: skipped symlink 'cur'/, - 'par' => /WARNING: skipped symlink 'par'/, + 'cur' => /WARNING: skipped symlink '.*\/cur'/, + 'par' => /WARNING: skipped symlink '.*\/par'/, 'par/moo' => :error } in_tmpdir do @@ -121,8 +129,8 @@ def test_two_directory_symlinks_a def test_two_directory_symlinks_b # Can't create par/moo, because the symlinks are skipped. entries = { - 'cur' => /WARNING: skipped symlink 'cur'/, - 'cur/par' => /WARNING: skipped symlink 'cur\/par'/, + 'cur' => /WARNING: skipped symlink '.*\/cur'/, + 'cur/par' => /WARNING: skipped symlink '.*\/cur\/par'/, 'par/moo' => :error } in_tmpdir do @@ -132,14 +140,29 @@ def test_two_directory_symlinks_b end end - def test_entry_name_with_absolute_path_does_not_extract - entries = { - '/tmp/' => /WARNING: skipped '\/tmp\/'/, - '/tmp/file.txt' => /WARNING: skipped '\/tmp\/file.txt'/ - } - in_tmpdir do - extract_paths(['tuzovakaoff', 'absolutepath.zip'], entries) + def test_entry_name_with_absolute_path_does_not_extract_by_accident + in_tmpdir do |test_path| + zip_path = File.join(TEST_FILE_ROOT, 'tuzovakaoff', 'absolutepath.zip') + Zip::File.open(zip_path) do |zip_file| + zip_file.each do |entry| + entry.extract(entry.name, destination_directory: nil) + assert File.exist?(File.join(test_path, entry.name)) + refute File.exist?(entry.name) unless entry.name == '/tmp/' + end + end + end + end + + def test_entry_name_with_absolute_path_extracts_to_cwd_by_default + in_tmpdir do |test_path| + zip_path = File.join(TEST_FILE_ROOT, 'tuzovakaoff', 'absolutepath.zip') + Zip::File.open(zip_path) do |zip_file| + zip_file.each(&:extract) + end + + # Check that only the relative file is created. refute File.exist?('/tmp/file.txt') + assert File.exist?(File.join(test_path, 'tmp', 'file.txt')) end end @@ -148,17 +171,20 @@ def test_entry_name_with_absolute_path_extract_when_given_different_path zip_path = File.join(TEST_FILE_ROOT, 'tuzovakaoff', 'absolutepath.zip') Zip::File.open(zip_path) do |zip_file| zip_file.each do |entry| - entry.extract(File.join(test_path, entry.name)) + entry.extract(destination_directory: test_path) end end + + # Check that only the relative file is created. refute File.exist?('/tmp/file.txt') + assert File.exist?(File.join(test_path, 'tmp', 'file.txt')) end end def test_entry_name_with_relative_symlink # Doesn't create the symlink path, so can't create path/file.txt. entries = { - 'path' => /WARNING: skipped symlink 'path'/, + 'path' => /WARNING: skipped symlink '.*\/path'/, 'path/file.txt' => :error } in_tmpdir do From ccc3d4ff1aad10f848d346bd18b6ccd3f76888b4 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 6 Nov 2022 15:02:57 +0000 Subject: [PATCH 424/469] Set version to be 3.0.0.alpha. --- lib/zip/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zip/version.rb b/lib/zip/version.rb index 633899d6..dd203608 100644 --- a/lib/zip/version.rb +++ b/lib/zip/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Zip - VERSION = '3.0.0' + VERSION = '3.0.0.alpha' end From edeab0713c67e398af181b490ff2b1fc100b7ebc Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 6 Nov 2022 17:46:29 +0000 Subject: [PATCH 425/469] Add RDoc and tasks to the Rakefile. --- .gitignore | 1 + Rakefile | 9 +++++++++ rubyzip.gemspec | 1 + 3 files changed, 11 insertions(+) diff --git a/.gitignore b/.gitignore index 08ebec6b..17c0cbd8 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ Gemfile.lock +samples/*.zip.* samples/zipdialogui.rb coverage +html/ pkg/ .ruby-gemset .ruby-version diff --git a/Rakefile b/Rakefile index 4326fdf9..73299026 100644 --- a/Rakefile +++ b/Rakefile @@ -2,6 +2,7 @@ require 'bundler/gem_tasks' require 'rake/testtask' +require 'rdoc/task' require 'rubocop/rake_task' task default: :test @@ -13,4 +14,12 @@ Rake::TestTask.new(:test) do |test| test.verbose = true end +RDoc::Task.new do |rdoc| + rdoc.main = 'README.md' + rdoc.rdoc_files.include('README.md', 'lib/**/*.rb') + rdoc.options << '--markup=markdown' + rdoc.options << '--tab-width=2' + rdoc.options << "-t Rubyzip version #{::Zip::VERSION}" +end + RuboCop::RakeTask.new diff --git a/rubyzip.gemspec b/rubyzip.gemspec index 5287d0cd..4c557675 100644 --- a/rubyzip.gemspec +++ b/rubyzip.gemspec @@ -29,6 +29,7 @@ Gem::Specification.new do |s| s.add_development_dependency 'minitest', '~> 5.4' s.add_development_dependency 'rake', '~> 12.3.3' + s.add_development_dependency 'rdoc', '~> 6.4.0' s.add_development_dependency 'rubocop', '~> 1.12.0' s.add_development_dependency 'rubocop-performance', '~> 1.10.0' s.add_development_dependency 'rubocop-rake', '~> 0.5.0' From 016e1000baff6ee8517f6b4395ca8fec9849cdb6 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 6 Nov 2022 17:59:13 +0000 Subject: [PATCH 426/469] Suppress Rubocop extension notice. --- .rubocop.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.rubocop.yml b/.rubocop.yml index 1997fe39..056c5164 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -7,6 +7,7 @@ inherit_from: .rubocop_todo.yml # Set this to the minimum supported ruby in the gemspec. Otherwise # we get errors if our ruby version doesn't match. AllCops: + SuggestExtensions: false TargetRubyVersion: 2.5 NewCops: enable From 0aa10bf7d5f61aa3e51fc87cf1fe67c7f39c212a Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 6 Nov 2022 17:59:34 +0000 Subject: [PATCH 427/469] Document or hide modules from the docs. --- lib/zip.rb | 5 ++++ lib/zip/crypto/null_encryption.rb | 2 +- lib/zip/crypto/traditional_encryption.rb | 2 +- lib/zip/dirtyable.rb | 4 +-- lib/zip/file_split.rb | 2 +- lib/zip/filesystem.rb | 32 ++++++++++++---------- lib/zip/ioextras.rb | 4 +-- lib/zip/ioextras/abstract_input_stream.rb | 4 +-- lib/zip/ioextras/abstract_output_stream.rb | 4 +-- lib/zip/null_decompressor.rb | 2 +- lib/zip/null_input_stream.rb | 2 +- 11 files changed, 35 insertions(+), 28 deletions(-) diff --git a/lib/zip.rb b/lib/zip.rb index 82c52003..b2ccc678 100644 --- a/lib/zip.rb +++ b/lib/zip.rb @@ -35,6 +35,11 @@ require 'zip/streamable_directory' require 'zip/errors' +# Rubyzip is a ruby module for reading and writing zip files. +# +# The main entry points are File, InputStream and OutputStream. For a +# file/directory interface in the style of the standard ruby ::File and +# ::Dir APIs then `require 'zip/filesystem'` and see FileSystem. module Zip extend self attr_accessor :unicode_names, diff --git a/lib/zip/crypto/null_encryption.rb b/lib/zip/crypto/null_encryption.rb index ae33c40c..7cc1d019 100644 --- a/lib/zip/crypto/null_encryption.rb +++ b/lib/zip/crypto/null_encryption.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Zip - module NullEncryption + module NullEncryption # :nodoc: def header_bytesize 0 end diff --git a/lib/zip/crypto/traditional_encryption.rb b/lib/zip/crypto/traditional_encryption.rb index eef3b18d..dfc1bd3f 100644 --- a/lib/zip/crypto/traditional_encryption.rb +++ b/lib/zip/crypto/traditional_encryption.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Zip - module TraditionalEncryption + module TraditionalEncryption # :nodoc: def initialize(password) @password = password reset_keys! diff --git a/lib/zip/dirtyable.rb b/lib/zip/dirtyable.rb index 78b1c86a..4e2104e4 100644 --- a/lib/zip/dirtyable.rb +++ b/lib/zip/dirtyable.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Zip - module Dirtyable # :nodoc: + module Dirtyable # :nodoc:all def initialize(dirty_on_create: true) @dirty = dirty_on_create end @@ -10,7 +10,7 @@ def dirty? @dirty end - module ClassMethods + module ClassMethods # :nodoc: def mark_dirty(*symbols) # :nodoc: # Move the original method and call it after we've set the dirty flag. symbols.each do |symbol| diff --git a/lib/zip/file_split.rb b/lib/zip/file_split.rb index 606d2e6b..0f6c694a 100644 --- a/lib/zip/file_split.rb +++ b/lib/zip/file_split.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Zip - module FileSplit #:nodoc: + module FileSplit # :nodoc: MAX_SEGMENT_SIZE = 3_221_225_472 MIN_SEGMENT_SIZE = 65_536 DATA_BUFFER_SIZE = 8192 diff --git a/lib/zip/filesystem.rb b/lib/zip/filesystem.rb index e223b3c0..5a65c974 100644 --- a/lib/zip/filesystem.rb +++ b/lib/zip/filesystem.rb @@ -19,25 +19,27 @@ module Zip # first.txt, a directory entry named mydir # and finally another normal entry named second.txt # - # require 'zip/filesystem' + # ``` + # require 'zip/filesystem' # - # Zip::File.open("my.zip", create: true) { - # |zipfile| - # zipfile.file.open("first.txt", "w") { |f| f.puts "Hello world" } - # zipfile.dir.mkdir("mydir") - # zipfile.file.open("mydir/second.txt", "w") { |f| f.puts "Hello again" } - # } + # Zip::File.open('my.zip', create: true) do |zipfile| + # zipfile.file.open('first.txt', 'w') { |f| f.puts 'Hello world' } + # zipfile.dir.mkdir('mydir') + # zipfile.file.open('mydir/second.txt', 'w') { |f| f.puts 'Hello again' } + # end + # ``` # # Reading is as easy as writing, as the following example shows. The # example writes the contents of first.txt from zip archive # my.zip to standard out. # - # require 'zip/filesystem' + # ``` + # require 'zip/filesystem' # - # Zip::File.open("my.zip") { - # |zipfile| - # puts zipfile.file.read("first.txt") - # } + # Zip::File.open('my.zip') do |zipfile| + # puts zipfile.file.read('first.txt') + # end + # ``` module FileSystem def initialize # :nodoc: mapped_zip = ZipFileNameMapper.new(self) @@ -47,14 +49,14 @@ def initialize # :nodoc: @zip_fs_file.dir = @zip_fs_dir end - # Returns a FileSystem::Dir which is much like ruby's builtin Dir (class) - # object, except it works on the Zip::File on which this method is + # Returns a Zip::FileSystem::Dir which is much like ruby's builtin Dir + # (class) object, except it works on the Zip::File on which this method is # invoked def dir @zip_fs_dir end - # Returns a FileSystem::File which is much like ruby's builtin File + # Returns a Zip::FileSystem::File which is much like ruby's builtin File # (class) object, except it works on the Zip::File on which this method is # invoked def file diff --git a/lib/zip/ioextras.rb b/lib/zip/ioextras.rb index df208da1..fbc77c6f 100644 --- a/lib/zip/ioextras.rb +++ b/lib/zip/ioextras.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Zip - module IOExtras #:nodoc: + module IOExtras # :nodoc: CHUNK_SIZE = 131_072 class << self @@ -20,7 +20,7 @@ def copy_stream_n(ostream, istream, nbytes) end # Implements kind_of? in order to pretend to be an IO object - module FakeIO + module FakeIO # :nodoc: def kind_of?(object) object == IO || super end diff --git a/lib/zip/ioextras/abstract_input_stream.rb b/lib/zip/ioextras/abstract_input_stream.rb index d0ea6825..721e33af 100644 --- a/lib/zip/ioextras/abstract_input_stream.rb +++ b/lib/zip/ioextras/abstract_input_stream.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true module Zip - module IOExtras + module IOExtras # :nodoc: # Implements many of the convenience methods of IO # such as gets, getc, readline and readlines # depends on: input_finished?, produce_input and read - module AbstractInputStream + module AbstractInputStream # :nodoc: include Enumerable include FakeIO diff --git a/lib/zip/ioextras/abstract_output_stream.rb b/lib/zip/ioextras/abstract_output_stream.rb index 28a09090..a71d24f6 100644 --- a/lib/zip/ioextras/abstract_output_stream.rb +++ b/lib/zip/ioextras/abstract_output_stream.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true module Zip - module IOExtras + module IOExtras # :nodoc: # Implements many of the output convenience methods of IO. # relies on << - module AbstractOutputStream + module AbstractOutputStream # :nodoc: include FakeIO def write(data) diff --git a/lib/zip/null_decompressor.rb b/lib/zip/null_decompressor.rb index 7b2557d4..8eb79e67 100644 --- a/lib/zip/null_decompressor.rb +++ b/lib/zip/null_decompressor.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Zip - module NullDecompressor #:nodoc:all + module NullDecompressor # :nodoc:all module_function def read(_length = nil, _outbuf = nil) diff --git a/lib/zip/null_input_stream.rb b/lib/zip/null_input_stream.rb index 69bc38d7..5c2c7077 100644 --- a/lib/zip/null_input_stream.rb +++ b/lib/zip/null_input_stream.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Zip - module NullInputStream #:nodoc:all + module NullInputStream # :nodoc:all include ::Zip::NullDecompressor include ::Zip::IOExtras::AbstractInputStream end From 2ffbfebb885a37a2c4ff4f8e473e248d14f24c3a Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 6 Nov 2022 21:26:37 +0000 Subject: [PATCH 428/469] Document or hide classes from the docs. --- lib/zip/central_directory.rb | 2 +- lib/zip/compressor.rb | 2 +- lib/zip/crypto/decrypted_io.rb | 2 +- lib/zip/crypto/encryption.rb | 4 +- lib/zip/crypto/null_encryption.rb | 4 +- lib/zip/crypto/traditional_encryption.rb | 4 +- lib/zip/decompressor.rb | 2 +- lib/zip/deflater.rb | 2 +- lib/zip/dos_time.rb | 2 +- lib/zip/entry.rb | 1 + lib/zip/entry_set.rb | 2 +- lib/zip/errors.rb | 14 ++++++ lib/zip/extra_field.rb | 2 +- lib/zip/extra_field/generic.rb | 2 +- lib/zip/extra_field/ntfs.rb | 2 +- lib/zip/extra_field/old_unix.rb | 2 +- lib/zip/extra_field/universal_time.rb | 2 +- lib/zip/extra_field/unix.rb | 2 +- lib/zip/extra_field/unknown.rb | 2 +- lib/zip/extra_field/zip64.rb | 2 +- lib/zip/file.rb | 57 ++++++++++++------------ lib/zip/inflater.rb | 2 +- lib/zip/null_compressor.rb | 2 +- lib/zip/pass_thru_compressor.rb | 2 +- lib/zip/pass_thru_decompressor.rb | 2 +- lib/zip/streamable_directory.rb | 2 +- 26 files changed, 70 insertions(+), 54 deletions(-) diff --git a/lib/zip/central_directory.rb b/lib/zip/central_directory.rb index 62de599b..53d213f5 100644 --- a/lib/zip/central_directory.rb +++ b/lib/zip/central_directory.rb @@ -5,7 +5,7 @@ require_relative 'dirtyable' module Zip - class CentralDirectory + class CentralDirectory # :nodoc: extend Forwardable include Dirtyable diff --git a/lib/zip/compressor.rb b/lib/zip/compressor.rb index 8c0680e0..3622da80 100644 --- a/lib/zip/compressor.rb +++ b/lib/zip/compressor.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Zip - class Compressor #:nodoc:all + class Compressor # :nodoc:all def finish; end end end diff --git a/lib/zip/crypto/decrypted_io.rb b/lib/zip/crypto/decrypted_io.rb index 92ccde63..db844a24 100644 --- a/lib/zip/crypto/decrypted_io.rb +++ b/lib/zip/crypto/decrypted_io.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Zip - class DecryptedIo #:nodoc:all + class DecryptedIo # :nodoc:all CHUNK_SIZE = 32_768 def initialize(io, decrypter) diff --git a/lib/zip/crypto/encryption.rb b/lib/zip/crypto/encryption.rb index c792e38b..b9c96c67 100644 --- a/lib/zip/crypto/encryption.rb +++ b/lib/zip/crypto/encryption.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true module Zip - class Encrypter #:nodoc:all + class Encrypter # :nodoc:all end - class Decrypter + class Decrypter # :nodoc:all end end diff --git a/lib/zip/crypto/null_encryption.rb b/lib/zip/crypto/null_encryption.rb index 7cc1d019..2ae48b70 100644 --- a/lib/zip/crypto/null_encryption.rb +++ b/lib/zip/crypto/null_encryption.rb @@ -11,7 +11,7 @@ def gp_flags end end - class NullEncrypter < Encrypter + class NullEncrypter < Encrypter # :nodoc: include NullEncryption def header(_mtime) @@ -29,7 +29,7 @@ def data_descriptor(_crc32, _compressed_size, _uncomprssed_size) def reset!; end end - class NullDecrypter < Decrypter + class NullDecrypter < Decrypter # :nodoc: include NullEncryption def decrypt(data) diff --git a/lib/zip/crypto/traditional_encryption.rb b/lib/zip/crypto/traditional_encryption.rb index dfc1bd3f..0f609048 100644 --- a/lib/zip/crypto/traditional_encryption.rb +++ b/lib/zip/crypto/traditional_encryption.rb @@ -38,7 +38,7 @@ def decrypt_byte end end - class TraditionalEncrypter < Encrypter + class TraditionalEncrypter < Encrypter # :nodoc: include TraditionalEncryption def header(mtime) @@ -72,7 +72,7 @@ def encode(num) end end - class TraditionalDecrypter < Decrypter + class TraditionalDecrypter < Decrypter # :nodoc: include TraditionalEncryption def decrypt(data) diff --git a/lib/zip/decompressor.rb b/lib/zip/decompressor.rb index b6bb9cc8..e75aba92 100644 --- a/lib/zip/decompressor.rb +++ b/lib/zip/decompressor.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Zip - class Decompressor #:nodoc:all + class Decompressor # :nodoc:all CHUNK_SIZE = 32_768 def self.decompressor_classes diff --git a/lib/zip/deflater.rb b/lib/zip/deflater.rb index 8f4827b4..a899e118 100644 --- a/lib/zip/deflater.rb +++ b/lib/zip/deflater.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Zip - class Deflater < Compressor #:nodoc:all + class Deflater < Compressor # :nodoc:all def initialize(output_stream, level = Zip.default_compression, encrypter = NullEncrypter.new) super() @output_stream = output_stream diff --git a/lib/zip/dos_time.rb b/lib/zip/dos_time.rb index 9203d734..0d94f21c 100644 --- a/lib/zip/dos_time.rb +++ b/lib/zip/dos_time.rb @@ -3,7 +3,7 @@ require 'rubygems' module Zip - class DOSTime < Time #:nodoc:all + class DOSTime < Time # :nodoc:all # MS-DOS File Date and Time format as used in Interrupt 21H Function 57H: # Register CX, the Time: diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 9c42e079..38ebcb41 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -5,6 +5,7 @@ require_relative 'dirtyable' module Zip + # Zip::Entry represents an entry in a Zip archive. class Entry include Dirtyable diff --git a/lib/zip/entry_set.rb b/lib/zip/entry_set.rb index 11db9518..941e92e2 100644 --- a/lib/zip/entry_set.rb +++ b/lib/zip/entry_set.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Zip - class EntrySet #:nodoc:all + class EntrySet # :nodoc:all include Enumerable attr_reader :entry_set diff --git a/lib/zip/errors.rb b/lib/zip/errors.rb index 74bf3a3d..c757a8da 100644 --- a/lib/zip/errors.rb +++ b/lib/zip/errors.rb @@ -1,8 +1,11 @@ # frozen_string_literal: true module Zip + # The superclass for all rubyzip error types. Simply rescue this one if + # you don't need to know what sort of error has been raised. class Error < StandardError; end + # Error raised if an unsupported compression method is used. class CompressionMethodError < Error attr_reader :compression_method @@ -16,6 +19,7 @@ def message end end + # Error raised if there is a problem while decompressing an archive entry. class DecompressionError < Error attr_reader :zlib_error @@ -29,6 +33,8 @@ def message end end + # Error raised when trying to extract an archive entry over an + # existing file. class DestinationExistsError < Error def initialize(destination) super() @@ -41,6 +47,8 @@ def message end end + # Error raised when trying to add an entry to an archive where the + # entry name already exists. class EntryExistsError < Error def initialize(source, name) super() @@ -53,6 +61,7 @@ def message end end + # Error raised when an entry name is invalid. class EntryNameError < Error def initialize(name = nil) super() @@ -68,6 +77,8 @@ def message end end + # Error raised if an entry is larger on extraction than it is advertised + # to be. class EntrySizeError < Error attr_reader :entry @@ -81,12 +92,15 @@ def message end end + # Error raised if a split archive is read. Rubyzip does not support reading + # split archives. class SplitArchiveError < Error def message 'Rubyzip cannot extract from split archives at this time.' end end + # Error raised if there is not enough metadata for the entry to be streamed. class StreamingError < Error attr_reader :entry diff --git a/lib/zip/extra_field.rb b/lib/zip/extra_field.rb index 8a73c3c9..95dd5db2 100644 --- a/lib/zip/extra_field.rb +++ b/lib/zip/extra_field.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Zip - class ExtraField < Hash + class ExtraField < Hash # :nodoc:all ID_MAP = {} def initialize(binstr = nil, local: false) diff --git a/lib/zip/extra_field/generic.rb b/lib/zip/extra_field/generic.rb index b3db791a..137efe90 100644 --- a/lib/zip/extra_field/generic.rb +++ b/lib/zip/extra_field/generic.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Zip - class ExtraField::Generic + class ExtraField::Generic # :nodoc: def self.register_map return unless const_defined?(:HEADER_ID) diff --git a/lib/zip/extra_field/ntfs.rb b/lib/zip/extra_field/ntfs.rb index cd691b1d..be1541db 100644 --- a/lib/zip/extra_field/ntfs.rb +++ b/lib/zip/extra_field/ntfs.rb @@ -3,7 +3,7 @@ module Zip # PKWARE NTFS Extra Field (0x000a) # Only Tag 0x0001 is supported - class ExtraField::NTFS < ExtraField::Generic + class ExtraField::NTFS < ExtraField::Generic # :nodoc: HEADER_ID = [0x000A].pack('v') register_map diff --git a/lib/zip/extra_field/old_unix.rb b/lib/zip/extra_field/old_unix.rb index 351339fe..0407d5da 100644 --- a/lib/zip/extra_field/old_unix.rb +++ b/lib/zip/extra_field/old_unix.rb @@ -2,7 +2,7 @@ module Zip # Olf Info-ZIP Extra for UNIX uid/gid and file timestampes - class ExtraField::OldUnix < ExtraField::Generic + class ExtraField::OldUnix < ExtraField::Generic # :nodoc: HEADER_ID = 'UX' register_map diff --git a/lib/zip/extra_field/universal_time.rb b/lib/zip/extra_field/universal_time.rb index 63ef070e..b77b321b 100644 --- a/lib/zip/extra_field/universal_time.rb +++ b/lib/zip/extra_field/universal_time.rb @@ -2,7 +2,7 @@ module Zip # Info-ZIP Additional timestamp field - class ExtraField::UniversalTime < ExtraField::Generic + class ExtraField::UniversalTime < ExtraField::Generic # :nodoc: HEADER_ID = 'UT' register_map diff --git a/lib/zip/extra_field/unix.rb b/lib/zip/extra_field/unix.rb index f88f1355..59207a23 100644 --- a/lib/zip/extra_field/unix.rb +++ b/lib/zip/extra_field/unix.rb @@ -2,7 +2,7 @@ module Zip # Info-ZIP Extra for UNIX uid/gid - class ExtraField::IUnix < ExtraField::Generic + class ExtraField::IUnix < ExtraField::Generic # :nodoc: HEADER_ID = 'Ux' register_map diff --git a/lib/zip/extra_field/unknown.rb b/lib/zip/extra_field/unknown.rb index 84e87f34..46f1f03d 100644 --- a/lib/zip/extra_field/unknown.rb +++ b/lib/zip/extra_field/unknown.rb @@ -2,7 +2,7 @@ module Zip # A class to hold unknown extra fields so that they are preserved. - class ExtraField::Unknown + class ExtraField::Unknown # :nodoc: def initialize @local_bin = +'' @cdir_bin = +'' diff --git a/lib/zip/extra_field/zip64.rb b/lib/zip/extra_field/zip64.rb index ba7b5110..14778970 100644 --- a/lib/zip/extra_field/zip64.rb +++ b/lib/zip/extra_field/zip64.rb @@ -2,7 +2,7 @@ module Zip # Info-ZIP Extra for Zip64 size - class ExtraField::Zip64 < ExtraField::Generic + class ExtraField::Zip64 < ExtraField::Generic # :nodoc: attr_accessor :compressed_size, :disk_start_number, :original_size, :relative_header_offset diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 69c73f5e..9c66dc13 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -5,48 +5,49 @@ require_relative 'file_split' module Zip - # ZipFile is modeled after java.util.zip.ZipFile from the Java SDK. - # The most important methods are those inherited from - # ZipCentralDirectory for accessing information about the entries in - # the archive and methods such as get_input_stream and - # get_output_stream for reading from and writing entries to the + # Zip::File is modeled after java.util.zip.ZipFile from the Java SDK. + # The most important methods are those for accessing information about + # the entries in + # the archive and methods such as `get_input_stream` and + # `get_output_stream` for reading from and writing entries to the # archive. The class includes a few convenience methods such as - # #extract for extracting entries to the filesystem, and #remove, - # #replace, #rename and #mkdir for making simple modifications to + # `extract` for extracting entries to the filesystem, and `remove`, + # `replace`, `rename` and `mkdir` for making simple modifications to # the archive. # - # Modifications to a zip archive are not committed until #commit or - # #close is called. The method #open accepts a block following - # the pattern from File.open offering a simple way to + # Modifications to a zip archive are not committed until `commit` or + # `close` is called. The method `open` accepts a block following + # the pattern from ::File.open offering a simple way to # automatically close the archive when the block returns. # - # The following example opens zip archive my.zip + # The following example opens zip archive `my.zip` # (creating it if it doesn't exist) and adds an entry - # first.txt and a directory entry a_dir + # `first.txt` and a directory entry `a_dir` # to it. # - # require 'zip' + # ``` + # require 'zip' # - # Zip::File.open("my.zip", create: true) { - # |zipfile| - # zipfile.get_output_stream("first.txt") { |f| f.puts "Hello from ZipFile" } - # zipfile.mkdir("a_dir") - # } + # Zip::File.open('my.zip', create: true) do |zipfile| + # zipfile.get_output_stream('first.txt') { |f| f.puts 'Hello from Zip::File' } + # zipfile.mkdir('a_dir') + # end + # ``` # - # The next example reopens my.zip writes the contents of - # first.txt to standard out and deletes the entry from + # The next example reopens `my.zip`, writes the contents of + # `first.txt` to standard out and deletes the entry from # the archive. # - # require 'zip' + # ``` + # require 'zip' # - # Zip::File.open("my.zip", create: true) { - # |zipfile| - # puts zipfile.read("first.txt") - # zipfile.remove("first.txt") - # } + # Zip::File.open('my.zip', create: true) do |zipfile| + # puts zipfile.read('first.txt') + # zipfile.remove('first.txt') + # end # - # ZipFileSystem offers an alternative API that emulates ruby's - # interface for accessing the filesystem, ie. the File and Dir classes. + # Zip::FileSystem offers an alternative API that emulates ruby's + # interface for accessing the filesystem, ie. the ::File and ::Dir classes. class File extend Forwardable extend FileSplit diff --git a/lib/zip/inflater.rb b/lib/zip/inflater.rb index 181603da..40f285f1 100644 --- a/lib/zip/inflater.rb +++ b/lib/zip/inflater.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Zip - class Inflater < Decompressor #:nodoc:all + class Inflater < Decompressor # :nodoc:all def initialize(*args) super diff --git a/lib/zip/null_compressor.rb b/lib/zip/null_compressor.rb index 41da0c61..c2ce899e 100644 --- a/lib/zip/null_compressor.rb +++ b/lib/zip/null_compressor.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Zip - class NullCompressor < Compressor #:nodoc:all + class NullCompressor < Compressor # :nodoc:all include Singleton def <<(_data) diff --git a/lib/zip/pass_thru_compressor.rb b/lib/zip/pass_thru_compressor.rb index 484d0da1..9b5bdf3b 100644 --- a/lib/zip/pass_thru_compressor.rb +++ b/lib/zip/pass_thru_compressor.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Zip - class PassThruCompressor < Compressor #:nodoc:all + class PassThruCompressor < Compressor # :nodoc:all def initialize(output_stream) super() @output_stream = output_stream diff --git a/lib/zip/pass_thru_decompressor.rb b/lib/zip/pass_thru_decompressor.rb index c9973c8d..56e8bd75 100644 --- a/lib/zip/pass_thru_decompressor.rb +++ b/lib/zip/pass_thru_decompressor.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Zip - class PassThruDecompressor < Decompressor #:nodoc:all + class PassThruDecompressor < Decompressor # :nodoc:all def initialize(*args) super @read_so_far = 0 diff --git a/lib/zip/streamable_directory.rb b/lib/zip/streamable_directory.rb index a3c7c93b..2c931190 100644 --- a/lib/zip/streamable_directory.rb +++ b/lib/zip/streamable_directory.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Zip - class StreamableDirectory < Entry + class StreamableDirectory < Entry # :nodoc: def initialize(zipfile, entry, src_path = nil, permission = nil) super(zipfile, entry) From f5ea5a8708124866e13757da96056f1af9bc522f Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Fri, 18 Nov 2022 22:17:34 +0000 Subject: [PATCH 429/469] Update Actions to use checkout@v3. --- .github/workflows/lint.yml | 2 +- .github/workflows/tests.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 66c262fb..01d401e6 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout rubyzip code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install and set up ruby uses: ruby/setup-ruby@v1 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 49fa1a55..af30a8d9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,7 +18,7 @@ jobs: continue-on-error: ${{ endsWith(matrix.ruby, 'head') || matrix.os == 'windows' }} steps: - name: Checkout rubyzip code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install and set up ruby uses: ruby/setup-ruby@v1 @@ -55,7 +55,7 @@ jobs: continue-on-error: true steps: - name: Checkout rubyzip code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install and set up ruby uses: ruby/setup-ruby@v1 From f4a1a099623bcf8eb891520f4c0250e370ba9b4c Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 19 Nov 2022 07:30:47 +0000 Subject: [PATCH 430/469] Update the linter CI to use Ruby 2.6. --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 01d401e6..927e273f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,7 +12,7 @@ jobs: - name: Install and set up ruby uses: ruby/setup-ruby@v1 with: - ruby-version: '2.5' + ruby-version: '2.6' bundler-cache: true - name: Rubocop From e672c46895974fc3916e8ab1250a7dbf2ba153c3 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 19 Nov 2022 07:32:46 +0000 Subject: [PATCH 431/469] Use an updated rubygems in the tests CI. This is needed for Ruby 2.5, which we need to support a bit longer. --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index af30a8d9..a7de14a7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -24,6 +24,7 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} + rubygems: '3.2.3' bundler-cache: true - name: Install other dependencies From c0a3c95eee50be2873699dd2cc8b15daacc49dc8 Mon Sep 17 00:00:00 2001 From: Peter Goldstein Date: Sun, 25 Dec 2022 11:10:52 -0500 Subject: [PATCH 432/469] Add Ruby 3.2 to the CI matrix. --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a7de14a7..6ae5ee3a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,7 +8,7 @@ jobs: fail-fast: false matrix: os: [ubuntu] - ruby: ['2.5', '2.6', '2.7', '3.0', '3.1', head, jruby, jruby-head, truffleruby, truffleruby-head] + ruby: ['2.5', '2.6', '2.7', '3.0', '3.1', '3.2', head, jruby, jruby-head, truffleruby, truffleruby-head] include: - os: macos ruby: '2.5' From efa00356340e11a35ffaf6db8b399dfc45a3e90d Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 3 Jan 2023 15:47:18 +0000 Subject: [PATCH 433/469] Turn on yjit for Ruby 3.2 in CI. --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6ae5ee3a..b2823a17 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -51,7 +51,7 @@ jobs: fail-fast: false matrix: os: [ubuntu, macos] - ruby: ['3.1', head] + ruby: ['3.1', '3.2', head] runs-on: ${{ matrix.os }}-latest continue-on-error: true steps: From 3ce504fcfb586fbd7aa260302ca50b956aba4063 Mon Sep 17 00:00:00 2001 From: Peter Boling Date: Tue, 31 Jan 2023 06:43:13 +0700 Subject: [PATCH 434/469] Fix typo know -> known --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3093e6b8..9dc3f73f 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ The public API of some classes has been modernized to use named parameters for o Version 3.x requires at least Ruby 2.5. -Version 2.x requires at least Ruby 2.4, and is know to work on Ruby 3.1. +Version 2.x requires at least Ruby 2.4, and is known to work on Ruby 3.1. It is not recommended to use any versions of Rubyzip earlier than 2.3 due to security issues. From a4f9ec64234ce8b41885643cb1fb19b68be17b4d Mon Sep 17 00:00:00 2001 From: OZAWA Sakuro <10973+sakuro@users.noreply.github.com> Date: Mon, 16 Jan 2023 11:06:59 +0900 Subject: [PATCH 435/469] Add compatibility test for Zip::InputStream#read(0) --- test/input_stream_test.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/input_stream_test.rb b/test/input_stream_test.rb index e1b92073..3fd27985 100644 --- a/test/input_stream_test.rb +++ b/test/input_stream_test.rb @@ -189,6 +189,12 @@ def test_read_with_number_of_bytes_returns_nil_at_eof end end + def test_read_with_zero_returns_empty_string + ::Zip::InputStream.open(TestZipFile::TEST_ZIP2.zip_name) do |zis| + assert_equal('', zis.read(0)) + end + end + def test_rewind ::Zip::InputStream.open(TestZipFile::TEST_ZIP2.zip_name) do |zis| e = zis.get_next_entry From a2fc20db8f5b15f54dd656f11f2f5c6f2bc1514b Mon Sep 17 00:00:00 2001 From: OZAWA Sakuro <10973+sakuro@users.noreply.github.com> Date: Mon, 16 Jan 2023 11:19:03 +0900 Subject: [PATCH 436/469] Zip::InputStream#read returns '' with 0 --- lib/zip/ioextras/abstract_input_stream.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zip/ioextras/abstract_input_stream.rb b/lib/zip/ioextras/abstract_input_stream.rb index 721e33af..c1327252 100644 --- a/lib/zip/ioextras/abstract_input_stream.rb +++ b/lib/zip/ioextras/abstract_input_stream.rb @@ -36,7 +36,7 @@ def read(number_of_bytes = nil, buf = +'') end if tbuf.nil? || tbuf.empty? - return nil if number_of_bytes + return nil if number_of_bytes&.positive? return '' end From 8cec9491b2cbdf730e8ee3293df9f7253f7c8099 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 16 Apr 2023 16:13:30 +0100 Subject: [PATCH 437/469] README: add link to wiki for version 3 details. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9dc3f73f..cf2adb81 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ The public API of some classes has been modernized to use named parameters for o * `InputStream` * `OutputStream` +**Please see [Updating to version 3.x](https://github.com/rubyzip/rubyzip/wiki/Updating-to-version-3.x) in the wiki for details.** + ## Requirements Version 3.x requires at least Ruby 2.5. From bb09f90ef963dbc92f125635425df34d3b030b99 Mon Sep 17 00:00:00 2001 From: MSP-Greg Date: Fri, 28 Jul 2023 17:27:06 -0500 Subject: [PATCH 438/469] Action - add 2 Windows head builds --- .github/workflows/tests.yml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b2823a17..9f03ac34 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,10 +10,11 @@ jobs: os: [ubuntu] ruby: ['2.5', '2.6', '2.7', '3.0', '3.1', '3.2', head, jruby, jruby-head, truffleruby, truffleruby-head] include: - - os: macos - ruby: '2.5' - - os: windows - ruby: '2.5' + - { os: macos , ruby: '2.5' } + - { os: windows, ruby: '2.5' } + # head builds + - { os: windows, ruby: ucrt } + - { os: windows, ruby: mswin } runs-on: ${{ matrix.os }}-latest continue-on-error: ${{ endsWith(matrix.ruby, 'head') || matrix.os == 'windows' }} steps: @@ -27,10 +28,6 @@ jobs: rubygems: '3.2.3' bundler-cache: true - - name: Install other dependencies - if: matrix.os == 'windows' - run: choco install zip - - name: Run the tests env: RUBYOPT: -v From f811b2d00e04d41d702398efe3fb54dbac892826 Mon Sep 17 00:00:00 2001 From: MSP-Greg Date: Fri, 28 Jul 2023 17:39:26 -0500 Subject: [PATCH 439/469] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cf2adb81..1172a4cc 100644 --- a/README.md +++ b/README.md @@ -373,10 +373,10 @@ Please see the table below for what we think the current situation is. Note: an | OS/Ruby | 2.5 | 2.6 | 2.7 | 3.0 | 3.1 | 3.1 +YJIT | Head | Head +YJIT | JRuby 9.3.2.0 | JRuby Head | Truffleruby 21.3.0 | Truffleruby Head | |---------|-----|-----|-----|-----|-----|----------|------|-----------|----------------|------------|--------------------|------------------| -|Ubuntu 20.04.3| CI | CI | CI | CI | CI | ci | ci | ci | CI | ci | CI | ci | -|Mac OS 11.6.2| CI | x | x | x | x | ci | | ci | x | | x | | +|Ubuntu 22.04| CI | CI | CI | CI | CI | ci | ci | ci | CI | ci | CI | ci | +|Mac OS 12.6.7| CI | x | x | x | x | ci | | ci | x | | x | | |Windows 10| | | x | | | | | | | | | | -|Windows Server 2019| CI | | | | | | | | | | | | +|Windows Server 2022| CI | | | | | | CI mswin
CI ucrt | | | | | | Key: `CI` - tested in CI, should work; `ci` - tested in CI, might fail; `x` - known working; `o` - known failing. From 5e9a9cbf18c0564fa1e62d86643935f03b36bde5 Mon Sep 17 00:00:00 2001 From: m-nakamura145 Date: Thu, 1 Feb 2024 22:49:34 +0900 Subject: [PATCH 440/469] Add Ruby 3.3 to CI matrix --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9f03ac34..386de7a0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,7 +8,7 @@ jobs: fail-fast: false matrix: os: [ubuntu] - ruby: ['2.5', '2.6', '2.7', '3.0', '3.1', '3.2', head, jruby, jruby-head, truffleruby, truffleruby-head] + ruby: ['2.5', '2.6', '2.7', '3.0', '3.1', '3.2', '3.3', head, jruby, jruby-head, truffleruby, truffleruby-head] include: - { os: macos , ruby: '2.5' } - { os: windows, ruby: '2.5' } @@ -48,7 +48,7 @@ jobs: fail-fast: false matrix: os: [ubuntu, macos] - ruby: ['3.1', '3.2', head] + ruby: ['3.1', '3.2', '3.3', head] runs-on: ${{ matrix.os }}-latest continue-on-error: true steps: From e3c173b0fc0d33155c7d161c24dc36a722804aaf Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 24 Feb 2024 17:36:52 +0000 Subject: [PATCH 441/469] Update the compatibility matrix in the README. --- README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1172a4cc..17735648 100644 --- a/README.md +++ b/README.md @@ -371,15 +371,19 @@ Rubyzip 2.3 is known to work on MRI 2.4 to 3.1 on Linux and Mac, and JRuby and T Please see the table below for what we think the current situation is. Note: an empty cell means "unknown", not "does not work". -| OS/Ruby | 2.5 | 2.6 | 2.7 | 3.0 | 3.1 | 3.1 +YJIT | Head | Head +YJIT | JRuby 9.3.2.0 | JRuby Head | Truffleruby 21.3.0 | Truffleruby Head | -|---------|-----|-----|-----|-----|-----|----------|------|-----------|----------------|------------|--------------------|------------------| -|Ubuntu 22.04| CI | CI | CI | CI | CI | ci | ci | ci | CI | ci | CI | ci | -|Mac OS 12.6.7| CI | x | x | x | x | ci | | ci | x | | x | | +| OS/Ruby | 2.5 | 2.6 | 2.7 | 3.0 | 3.1 | 3.2 | 3.3 | Head | JRuby 9.4.6.0 | JRuby Head | Truffleruby 23.1.2 | Truffleruby Head | +|---------|-----|-----|-----|-----|-----|-----|-----|------|---------------|------------|--------------------|------------------| +|Ubuntu 22.04| CI | CI | CI | CI | CI | CI | CI | ci | CI | ci | CI | ci | +|Mac OS 12.7.3| CI | x | x | ci | ci | ci | ci | ci | x | | x | | |Windows 10| | | x | | | | | | | | | | |Windows Server 2022| CI | | | | | | CI mswin
CI ucrt | | | | | | Key: `CI` - tested in CI, should work; `ci` - tested in CI, might fail; `x` - known working; `o` - known failing. +Ruby 3.0+ are also tested separately with YJIT turned on. + +See [the Actions tab](https://github.com/rubyzip/rubyzip/actions) in GitHub for full details. + Please [raise a PR](https://github.com/rubyzip/rubyzip/pulls) if you know Rubyzip works on a platform/Ruby combination not listed here, or [raise an issue](https://github.com/rubyzip/rubyzip/issues) if you see a failure where we think it should work. ## Developing From fd0cf5443ea95c781750f4c0ec735e6cec92ce38 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 24 Feb 2024 17:52:33 +0000 Subject: [PATCH 442/469] Update ZipCrypto instructions for 2.x versions. Suggested by @KamilDzierbicki in #568. --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index 17735648..c55dcd0f 100644 --- a/README.md +++ b/README.md @@ -205,6 +205,27 @@ Any attempt to move about in a zip file opened with `Zip::InputStream` could res Rubyzip supports reading/writing zip files with traditional zip encryption (a.k.a. "ZipCrypto"). AES encryption is not yet supported. It can be used with buffer streams, e.g.: +#### Version 2.x + +```ruby +# Writing. +enc = Zip::TraditionalEncrypter.new('password') +buffer = Zip::OutputStream.write_buffer(::StringIO.new(''), enc) do |output| + output.put_next_entry("my_file.txt") + output.write my_data +end + +# Reading. +dec = Zip::TraditionalDecrypter.new('password') +Zip::InputStream.open(buffer, 0, dec) do |input| + entry = input.get_next_entry + puts "Contents of '#{entry.name}':" + puts input.read +end +``` + +#### Version 3.x + ```ruby # Writing. enc = Zip::TraditionalEncrypter.new('password') From b1ee5cf27212622a1487370e21e4d7bf4067460b Mon Sep 17 00:00:00 2001 From: Kyle Huston Date: Mon, 16 Oct 2023 10:54:59 -0400 Subject: [PATCH 443/469] Note write_zip64_support default is false before 3.0 Improve accuracy of readme to note that write_zip64_support is enabled by default in versions 3.0 and later. --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c55dcd0f..de6db5e6 100644 --- a/README.md +++ b/README.md @@ -359,12 +359,14 @@ end ### Zip64 Support -By default, Zip64 support is enabled for writing. To disable it do this: +Since version 3.0, Zip64 support is enabled for writing by default. To disable it do this: ```ruby Zip.write_zip64_support = false ``` +Prior to version 3.0, Zip64 support is disabled for writing by default. + _NOTE_: If Zip64 write support is enabled then any extractor subsequently used may also require Zip64 support to read from the resultant archive. ### Block Form From 1c06454985b05d40cfb80def7164d800aaf49d70 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Fri, 1 Mar 2024 22:14:48 +0000 Subject: [PATCH 444/469] Update minimum ruby version to 3.0. All rubies before 3.0 are EOL and this is a major version bump, so it's the right time to do this. --- .github/workflows/lint.yml | 2 +- .github/workflows/tests.yml | 6 ++-- .rubocop.yml | 2 +- .rubocop_todo.yml | 3 ++ Rakefile | 2 +- lib/zip.rb | 2 +- lib/zip/central_directory.rb | 20 ++++++------- lib/zip/crypto/traditional_encryption.rb | 2 +- lib/zip/entry.rb | 34 +++++++++++----------- lib/zip/entry_set.rb | 4 +-- lib/zip/errors.rb | 6 ++-- lib/zip/extra_field.rb | 4 +-- lib/zip/extra_field/generic.rb | 6 ++-- lib/zip/extra_field/ntfs.rb | 4 +-- lib/zip/file.rb | 2 +- lib/zip/file_split.rb | 8 +---- lib/zip/filesystem/dir.rb | 4 +-- lib/zip/filesystem/zip_file_name_mapper.rb | 2 +- lib/zip/input_stream.rb | 7 ++--- lib/zip/ioextras.rb | 2 +- lib/zip/ioextras/abstract_input_stream.rb | 4 +-- lib/zip/output_stream.rb | 4 +-- rubyzip.gemspec | 29 +++++++++--------- samples/example_filesystem.rb | 2 +- samples/write_simple.rb | 2 +- test/case_sensitivity_test.rb | 6 ++-- test/deflater_test.rb | 2 +- test/encryption_test.rb | 6 ++-- test/file_extract_directory_test.rb | 2 +- test/file_extract_test.rb | 12 +++----- test/file_split_test.rb | 6 ++-- test/file_test.rb | 8 ++--- test/filesystem/file_nonmutating_test.rb | 8 ++--- test/gentestfiles.rb | 2 +- test/output_stream_test.rb | 6 ++-- test/settings_test.rb | 2 +- test/stored_support_test.rb | 8 ++--- test/test_helper.rb | 4 +-- 38 files changed, 114 insertions(+), 121 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 927e273f..0ba0e48c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,7 +12,7 @@ jobs: - name: Install and set up ruby uses: ruby/setup-ruby@v1 with: - ruby-version: '2.6' + ruby-version: '3.0' bundler-cache: true - name: Rubocop diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 386de7a0..46117a01 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,10 +8,10 @@ jobs: fail-fast: false matrix: os: [ubuntu] - ruby: ['2.5', '2.6', '2.7', '3.0', '3.1', '3.2', '3.3', head, jruby, jruby-head, truffleruby, truffleruby-head] + ruby: ['3.0', '3.1', '3.2', '3.3', head, jruby, jruby-head, truffleruby, truffleruby-head] include: - - { os: macos , ruby: '2.5' } - - { os: windows, ruby: '2.5' } + - { os: macos , ruby: '3.0' } + - { os: windows, ruby: '3.0' } # head builds - { os: windows, ruby: ucrt } - { os: windows, ruby: mswin } diff --git a/.rubocop.yml b/.rubocop.yml index 056c5164..49bed3dd 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -8,7 +8,7 @@ inherit_from: .rubocop_todo.yml # we get errors if our ruby version doesn't match. AllCops: SuggestExtensions: false - TargetRubyVersion: 2.5 + TargetRubyVersion: 3.0 NewCops: enable # Allow this in this file because adding the extra lines is pointless. diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 2b3f89b1..ff5238cd 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,6 +6,9 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. +Gemspec/DevelopmentDependencies: + Enabled: false + # Offense count: 7 Lint/MissingSuper: Exclude: diff --git a/Rakefile b/Rakefile index 73299026..f1e81470 100644 --- a/Rakefile +++ b/Rakefile @@ -19,7 +19,7 @@ RDoc::Task.new do |rdoc| rdoc.rdoc_files.include('README.md', 'lib/**/*.rb') rdoc.options << '--markup=markdown' rdoc.options << '--tab-width=2' - rdoc.options << "-t Rubyzip version #{::Zip::VERSION}" + rdoc.options << "-t Rubyzip version #{Zip::VERSION}" end RuboCop::RakeTask.new diff --git a/lib/zip.rb b/lib/zip.rb index b2ccc678..5d421226 100644 --- a/lib/zip.rb +++ b/lib/zip.rb @@ -65,7 +65,7 @@ def reset! @on_exists_proc = false @continue_on_exists_proc = false @sort_entries = false - @default_compression = ::Zlib::DEFAULT_COMPRESSION + @default_compression = Zlib::DEFAULT_COMPRESSION @write_zip64_support = true @warn_invalid_date = true @case_insensitive_match = false diff --git a/lib/zip/central_directory.rb b/lib/zip/central_directory.rb index 53d213f5..61be6fd3 100644 --- a/lib/zip/central_directory.rb +++ b/lib/zip/central_directory.rb @@ -28,7 +28,7 @@ class CentralDirectory # :nodoc: mark_dirty :<<, :comment=, :delete - def initialize(entries = EntrySet.new, comment = '') #:nodoc: + def initialize(entries = EntrySet.new, comment = '') # :nodoc: super(dirty_on_create: false) @entry_set = entries.kind_of?(EntrySet) ? entries : EntrySet.new(entries) @comment = comment @@ -39,7 +39,7 @@ def read_from_stream(io) read_central_directory_entries(io) end - def write_to_stream(io) #:nodoc: + def write_to_stream(io) # :nodoc: cdir_offset = io.tell @entry_set.each { |entry| entry.write_c_dir_entry(io) } eocd_offset = io.tell @@ -61,7 +61,7 @@ def count_entries(io) @size end - def ==(other) #:nodoc: + def ==(other) # :nodoc: return false unless other.kind_of?(CentralDirectory) @entry_set.entries.sort == other.entries.sort && comment == other.comment @@ -69,7 +69,7 @@ def ==(other) #:nodoc: private - def write_e_o_c_d(io, offset, cdir_size) #:nodoc: + def write_e_o_c_d(io, offset, cdir_size) # :nodoc: tmp = [ END_OF_CD_SIG, 0, # @numberOfThisDisk @@ -84,7 +84,7 @@ def write_e_o_c_d(io, offset, cdir_size) #:nodoc: io << @comment end - def write_64_e_o_c_d(io, offset, cdir_size) #:nodoc: + def write_64_e_o_c_d(io, offset, cdir_size) # :nodoc: tmp = [ ZIP64_END_OF_CD_SIG, 44, # size of zip64 end of central directory record (excludes signature and field itself) @@ -110,7 +110,7 @@ def write_64_eocd_locator(io, zip64_eocd_offset) io << tmp.pack('VVQ> 24).chr, ~@key2) end diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 38ebcb41..18959233 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -185,12 +185,12 @@ def ftype # :nodoc: # Dynamic checkers %w[directory file symlink].each do |k| - define_method "#{k}?" do + define_method :"#{k}?" do file_type_is?(k.to_sym) end end - def name_is_directory? #:nodoc:all + def name_is_directory? # :nodoc:all @name.end_with?('/') end @@ -207,7 +207,7 @@ def name_safe? ::File.absolute_path(cleanpath.to_s, root).match?(/([A-Z]:)?#{naive}/i) end - def local_entry_offset #:nodoc:all + def local_entry_offset # :nodoc:all local_header_offset + @local_header_size end @@ -223,7 +223,7 @@ def comment_size @comment ? @comment.bytesize : 0 end - def calculate_local_header_size #:nodoc:all + def calculate_local_header_size # :nodoc:all LOCAL_ENTRY_STATIC_HEADER_LENGTH + name_size + extra_size end @@ -239,12 +239,12 @@ def verify_local_header_size! "Local header size changed (#{@local_header_size} -> #{new_size})" end - def cdir_header_size #:nodoc:all + def cdir_header_size # :nodoc:all CDIR_ENTRY_STATIC_HEADER_LENGTH + name_size + (@extra ? @extra.c_dir_size : 0) + comment_size end - def next_header_offset #:nodoc:all + def next_header_offset # :nodoc:all local_entry_offset + compressed_size end @@ -266,7 +266,7 @@ def extract(entry_path = @name, destination_directory: '.', &block) raise "unknown file type #{inspect}" unless directory? || file? || symlink? - __send__("create_#{ftype}", extract_path, &block) + __send__(:"create_#{ftype}", extract_path, &block) self end @@ -275,7 +275,7 @@ def to_s end class << self - def read_c_dir_entry(io) #:nodoc:all + def read_c_dir_entry(io) # :nodoc:all path = if io.respond_to?(:path) io.path else @@ -314,7 +314,7 @@ def unpack_local_entry(buf) @extra_length = buf.unpack('VCCvvvvVVVvv') end - def read_local_entry(io) #:nodoc:all + def read_local_entry(io) # :nodoc:all @dirty = false # No changes at this point. @local_header_offset = io.tell @@ -371,7 +371,7 @@ def pack_local_entry @extra ? @extra.local_size : 0].pack('VvvvvvVVVvv') end - def write_local_entry(io, rewrite: false) #:nodoc:all + def write_local_entry(io, rewrite: false) # :nodoc:all prep_local_zip64_extra verify_local_header_size! if rewrite @local_header_offset = io.tell @@ -463,7 +463,7 @@ def read_extra_field(buf, local: false) end end - def read_c_dir_entry(io) #:nodoc:all + def read_c_dir_entry(io) # :nodoc:all @dirty = false # No changes at this point. static_sized_fields_buf = io.read(::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH) check_c_dir_entry_static_header_length(static_sized_fields_buf) @@ -556,7 +556,7 @@ def pack_c_dir_entry ].pack('VCCvvvvvVVVvvvvvVV') end - def write_c_dir_entry(io) #:nodoc:all + def write_c_dir_entry(io) # :nodoc:all prep_cdir_zip64_extra case @fstype @@ -574,7 +574,7 @@ def write_c_dir_entry(io) #:nodoc:all end unless ft.nil? - @external_file_attributes = (ft << 12 | (@unix_perms & 0o7777)) << 16 + @external_file_attributes = ((ft << 12) | (@unix_perms & 0o7777)) << 16 end end @@ -639,7 +639,7 @@ def gather_fileinfo_from_srcpath(src_path) # :nodoc: if name_is_directory? raise ArgumentError, "entry name '#{newEntry}' indicates directory entry, but " \ - "'#{src_path}' is not a directory" + "'#{src_path}' is not a directory" end :file when 'directory' @@ -649,7 +649,7 @@ def gather_fileinfo_from_srcpath(src_path) # :nodoc: if name_is_directory? raise ArgumentError, "entry name '#{newEntry}' indicates directory entry, but " \ - "'#{src_path}' is not a directory" + "'#{src_path}' is not a directory" end :symlink else @@ -661,7 +661,7 @@ def gather_fileinfo_from_srcpath(src_path) # :nodoc: get_extra_attributes_from_path(@filepath) end - def write_to_zip_output_stream(zip_output_stream) #:nodoc:all + def write_to_zip_output_stream(zip_output_stream) # :nodoc:all if ftype == :directory zip_output_stream.put_next_entry(self) elsif @filepath @@ -749,7 +749,7 @@ def create_symlink(dest_path) # apply missing data from the zip64 extra information field, if present # (required when file sizes exceed 2**32, but can be used for all files) - def parse_zip64_extra(for_local_header) #:nodoc:all + def parse_zip64_extra(for_local_header) # :nodoc:all return unless zip64? if for_local_header diff --git a/lib/zip/entry_set.rb b/lib/zip/entry_set.rb index 941e92e2..cb4700d6 100644 --- a/lib/zip/entry_set.rb +++ b/lib/zip/entry_set.rb @@ -61,12 +61,12 @@ def parent(entry) end def glob(pattern, flags = ::File::FNM_PATHNAME | ::File::FNM_DOTMATCH | ::File::FNM_EXTGLOB) - entries.map do |entry| + entries.filter_map do |entry| next nil unless ::File.fnmatch(pattern, entry.name.chomp('/'), flags) yield(entry) if block_given? entry - end.compact + end end protected diff --git a/lib/zip/errors.rb b/lib/zip/errors.rb index c757a8da..5b75e9a6 100644 --- a/lib/zip/errors.rb +++ b/lib/zip/errors.rb @@ -43,7 +43,7 @@ def initialize(destination) def message "Cannot create file or directory '#{@destination}'. " \ - 'A file already exists with that name.' + 'A file already exists with that name.' end end @@ -111,8 +111,8 @@ def initialize(entry) def message "The local header of this entry ('#{@entry.name}') does not contain " \ - 'the correct metadata for `Zip::InputStream` to be able to ' \ - 'uncompress it. Please use `Zip::File` instead of `Zip::InputStream`.' + 'the correct metadata for `Zip::InputStream` to be able to ' \ + 'uncompress it. Please use `Zip::File` instead of `Zip::InputStream`.' end end end diff --git a/lib/zip/extra_field.rb b/lib/zip/extra_field.rb index 95dd5db2..31d75d2c 100644 --- a/lib/zip/extra_field.rb +++ b/lib/zip/extra_field.rb @@ -21,8 +21,8 @@ def extra_field_type_exist(binstr, id, len, index) def extra_field_type_unknown(binstr, len, index, local) self['Unknown'] ||= Unknown.new - if !len || len + 4 > binstr[index..-1].bytesize - self['Unknown'].merge(binstr[index..-1], local: local) + if !len || len + 4 > binstr[index..].bytesize + self['Unknown'].merge(binstr[index..], local: local) return end diff --git a/lib/zip/extra_field/generic.rb b/lib/zip/extra_field/generic.rb index 137efe90..ddd8eed6 100644 --- a/lib/zip/extra_field/generic.rb +++ b/lib/zip/extra_field/generic.rb @@ -21,17 +21,17 @@ def initial_parse(binstr) return false end - [binstr[2, 2].unpack1('v'), binstr[4..-1]] + [binstr[2, 2].unpack1('v'), binstr[4..]] end def to_local_bin s = pack_for_local - self.class.const_get(:HEADER_ID) + [s.bytesize].pack('v') << s + (self.class.const_get(:HEADER_ID) + [s.bytesize].pack('v')) << s end def to_c_dir_bin s = pack_for_c_dir - self.class.const_get(:HEADER_ID) + [s.bytesize].pack('v') << s + (self.class.const_get(:HEADER_ID) + [s.bytesize].pack('v')) << s end end end diff --git a/lib/zip/extra_field/ntfs.rb b/lib/zip/extra_field/ntfs.rb index be1541db..eac99ad6 100644 --- a/lib/zip/extra_field/ntfs.rb +++ b/lib/zip/extra_field/ntfs.rb @@ -25,7 +25,7 @@ def merge(binstr) size, content = initial_parse(binstr) (size && content) || return - content = content[4..-1] + content = content[4..] tags = parse_tags(content) tag1 = tags[1] @@ -86,7 +86,7 @@ def parse_tags(content) end def from_ntfs_time(ntfs_time) - ::Zip::DOSTime.at(ntfs_time / WINDOWS_TICK - SEC_TO_UNIX_EPOCH) + ::Zip::DOSTime.at((ntfs_time / WINDOWS_TICK) - SEC_TO_UNIX_EPOCH) end def to_ntfs_time(time) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 9c66dc13..85a75035 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -356,7 +356,7 @@ def initialize_cdir(path_or_io, buffer: false) # This zip is probably a non-empty StringIO. @create = false @cdir.read_from_stream(path_or_io) - elsif !@create && ::File.zero?(@name) + elsif !@create && ::File.empty?(@name) # A file exists, but it is empty, and we've said we're # NOT creating a new zip. raise Error, "File #{@name} has zero size. Did you mean to pass the create flag?" diff --git a/lib/zip/file_split.rb b/lib/zip/file_split.rb index 0f6c694a..de5364a3 100644 --- a/lib/zip/file_split.rb +++ b/lib/zip/file_split.rb @@ -7,13 +7,7 @@ module FileSplit # :nodoc: DATA_BUFFER_SIZE = 8192 def get_segment_size_for_split(segment_size) - if MIN_SEGMENT_SIZE > segment_size - MIN_SEGMENT_SIZE - elsif MAX_SEGMENT_SIZE < segment_size - MAX_SEGMENT_SIZE - else - segment_size - end + segment_size.clamp(MIN_SEGMENT_SIZE, MAX_SEGMENT_SIZE) end def get_partial_zip_file_name(zip_file_name, partial_zip_file_name) diff --git a/lib/zip/filesystem/dir.rb b/lib/zip/filesystem/dir.rb index a5e1ef9e..5ae4ac45 100644 --- a/lib/zip/filesystem/dir.rb +++ b/lib/zip/filesystem/dir.rb @@ -45,8 +45,8 @@ def entries(directory_name) entries end - def glob(*args, &block) - @mapped_zip.glob(*args, &block) + def glob(...) + @mapped_zip.glob(...) end def foreach(directory_name) diff --git a/lib/zip/filesystem/zip_file_name_mapper.rb b/lib/zip/filesystem/zip_file_name_mapper.rb index 6b9c7f51..77a4e691 100644 --- a/lib/zip/filesystem/zip_file_name_mapper.rb +++ b/lib/zip/filesystem/zip_file_name_mapper.rb @@ -74,7 +74,7 @@ def expand_path(path) private def expand_to_entry(path) - expand_path(path)[1..-1] + expand_path(path)[1..] end end end diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index f48466c0..56ecf4f9 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -144,8 +144,7 @@ def open_entry 'A password is required to decode this zip file' end - if @current_entry.incomplete? && @current_entry.compressed_size == 0 \ - && !@complete_entry + if @current_entry.incomplete? && @current_entry.compressed_size == 0 && !@complete_entry raise StreamingError, @current_entry end @@ -166,8 +165,8 @@ def get_decompressor return ::Zip::NullDecompressor if @current_entry.nil? decompressed_size = - if @current_entry.incomplete? && @current_entry.crc == 0 \ - && @current_entry.size == 0 && @complete_entry + if @current_entry.incomplete? && @current_entry.crc == 0 && + @current_entry.size == 0 && @complete_entry @complete_entry.size else @current_entry.size diff --git a/lib/zip/ioextras.rb b/lib/zip/ioextras.rb index fbc77c6f..66688966 100644 --- a/lib/zip/ioextras.rb +++ b/lib/zip/ioextras.rb @@ -12,7 +12,7 @@ def copy_stream(ostream, istream) def copy_stream_n(ostream, istream, nbytes) toread = nbytes while toread > 0 && !istream.eof? - tr = toread > CHUNK_SIZE ? CHUNK_SIZE : toread + tr = [toread, CHUNK_SIZE].min ostream.write(istream.read(tr, +'')) toread -= tr end diff --git a/lib/zip/ioextras/abstract_input_stream.rb b/lib/zip/ioextras/abstract_input_stream.rb index c1327252..ed95e63d 100644 --- a/lib/zip/ioextras/abstract_input_stream.rb +++ b/lib/zip/ioextras/abstract_input_stream.rb @@ -76,13 +76,13 @@ def gets(a_sep_string = $INPUT_RECORD_SEPARATOR, number_of_bytes = nil) a_sep_string = "#{$INPUT_RECORD_SEPARATOR}#{$INPUT_RECORD_SEPARATOR}" if a_sep_string.empty? buffer_index = 0 - over_limit = (number_of_bytes && @output_buffer.bytesize >= number_of_bytes) + over_limit = number_of_bytes && @output_buffer.bytesize >= number_of_bytes while (match_index = @output_buffer.index(a_sep_string, buffer_index)).nil? && !over_limit buffer_index = [buffer_index, @output_buffer.bytesize - a_sep_string.bytesize].max return @output_buffer.empty? ? nil : flush if input_finished? @output_buffer << produce_input - over_limit = (number_of_bytes && @output_buffer.bytesize >= number_of_bytes) + over_limit = number_of_bytes && @output_buffer.bytesize >= number_of_bytes end sep_index = [ match_index + a_sep_string.bytesize, diff --git a/lib/zip/output_stream.rb b/lib/zip/output_stream.rb index 0c4e93c8..095126b5 100644 --- a/lib/zip/output_stream.rb +++ b/lib/zip/output_stream.rb @@ -139,8 +139,8 @@ def finalize_current_entry return unless @current_entry finish - @current_entry.compressed_size = @output_stream.tell - \ - @current_entry.local_header_offset - \ + @current_entry.compressed_size = @output_stream.tell - + @current_entry.local_header_offset - @current_entry.calculate_local_header_size @current_entry.size = @compressor.size @current_entry.crc = @compressor.crc diff --git a/rubyzip.gemspec b/rubyzip.gemspec index 4c557675..f9d85f1c 100644 --- a/rubyzip.gemspec +++ b/rubyzip.gemspec @@ -4,7 +4,7 @@ require_relative 'lib/zip/version' Gem::Specification.new do |s| s.name = 'rubyzip' - s.version = ::Zip::VERSION + s.version = Zip::VERSION s.authors = ['Robert Haines', 'John Lees-Miller', 'Alexander Simonov'] s.email = [ 'hainesr@gmail.com', 'jdleesmiller@gmail.com', 'alex@simonov.me' @@ -18,21 +18,22 @@ Gem::Specification.new do |s| s.license = 'BSD-2-Clause' s.metadata = { - 'bug_tracker_uri' => 'https://github.com/rubyzip/rubyzip/issues', - 'changelog_uri' => "https://github.com/rubyzip/rubyzip/blob/v#{s.version}/Changelog.md", - 'documentation_uri' => "https://www.rubydoc.info/gems/rubyzip/#{s.version}", - 'source_code_uri' => "https://github.com/rubyzip/rubyzip/tree/v#{s.version}", - 'wiki_uri' => 'https://github.com/rubyzip/rubyzip/wiki' + 'bug_tracker_uri' => 'https://github.com/rubyzip/rubyzip/issues', + 'changelog_uri' => "https://github.com/rubyzip/rubyzip/blob/v#{s.version}/Changelog.md", + 'documentation_uri' => "https://www.rubydoc.info/gems/rubyzip/#{s.version}", + 'source_code_uri' => "https://github.com/rubyzip/rubyzip/tree/v#{s.version}", + 'wiki_uri' => 'https://github.com/rubyzip/rubyzip/wiki', + 'rubygems_mfa_required' => 'true' } - s.required_ruby_version = '>= 2.5' + s.required_ruby_version = '>= 3.0' - s.add_development_dependency 'minitest', '~> 5.4' - s.add_development_dependency 'rake', '~> 12.3.3' - s.add_development_dependency 'rdoc', '~> 6.4.0' - s.add_development_dependency 'rubocop', '~> 1.12.0' - s.add_development_dependency 'rubocop-performance', '~> 1.10.0' - s.add_development_dependency 'rubocop-rake', '~> 0.5.0' - s.add_development_dependency 'simplecov', '~> 0.18.0' + s.add_development_dependency 'minitest', '~> 5.22.0' + s.add_development_dependency 'rake', '~> 13.1.0' + s.add_development_dependency 'rdoc', '~> 6.6.2' + s.add_development_dependency 'rubocop', '~> 1.61.0' + s.add_development_dependency 'rubocop-performance', '~> 1.20.0' + s.add_development_dependency 'rubocop-rake', '~> 0.6.0' + s.add_development_dependency 'simplecov', '~> 0.22.0' s.add_development_dependency 'simplecov-lcov', '~> 0.8' end diff --git a/samples/example_filesystem.rb b/samples/example_filesystem.rb index 6909848f..ec0075ee 100755 --- a/samples/example_filesystem.rb +++ b/samples/example_filesystem.rb @@ -7,7 +7,7 @@ EXAMPLE_ZIP = 'filesystem.zip' -File.delete(EXAMPLE_ZIP) if File.exist?(EXAMPLE_ZIP) +FileUtils.rm_f(EXAMPLE_ZIP) Zip::File.open(EXAMPLE_ZIP, create: true) do |zf| zf.file.open('file1.txt', 'w') { |os| os.write 'first file1.txt' } diff --git a/samples/write_simple.rb b/samples/write_simple.rb index f7939200..84ac1387 100755 --- a/samples/write_simple.rb +++ b/samples/write_simple.rb @@ -5,7 +5,7 @@ require 'zip' -::Zip::OutputStream.open('simple.zip') do |zos| +Zip::OutputStream.open('simple.zip') do |zos| zos.put_next_entry 'entry.txt' zos.puts 'Hello world' end diff --git a/test/case_sensitivity_test.rb b/test/case_sensitivity_test.rb index 07e84579..9a4d84f4 100644 --- a/test/case_sensitivity_test.rb +++ b/test/case_sensitivity_test.rb @@ -18,7 +18,7 @@ def teardown def test_add_case_sensitive ::Zip.case_insensitive_match = false - SRC_FILES.each { |fn, _en| assert(::File.exist?(fn)) } + SRC_FILES.each { |(fn, _en)| assert(::File.exist?(fn)) } zf = ::Zip::File.new(EMPTY_FILENAME, create: true) SRC_FILES.each { |fn, en| zf.add(en, fn) } @@ -37,7 +37,7 @@ def test_add_case_sensitive def test_add_case_insensitive ::Zip.case_insensitive_match = true - SRC_FILES.each { |fn, _en| assert(::File.exist?(fn)) } + SRC_FILES.each { |(fn, _en)| assert(::File.exist?(fn)) } zf = ::Zip::File.new(EMPTY_FILENAME, create: true) error = assert_raises Zip::EntryExistsError do @@ -50,7 +50,7 @@ def test_add_case_insensitive def test_add_case_sensitive_read_case_insensitive ::Zip.case_insensitive_match = false - SRC_FILES.each { |fn, _en| assert(::File.exist?(fn)) } + SRC_FILES.each { |(fn, _en)| assert(::File.exist?(fn)) } zf = ::Zip::File.new(EMPTY_FILENAME, create: true) SRC_FILES.each { |fn, en| zf.add(en, fn) } diff --git a/test/deflater_test.rb b/test/deflater_test.rb index fb782205..9e6b1e2e 100644 --- a/test/deflater_test.rb +++ b/test/deflater_test.rb @@ -49,7 +49,7 @@ def test_data_error private def load_file(filename) - File.open(filename, 'rb', &:read) + File.binread(filename) end def deflate(data, filename) diff --git a/test/encryption_test.rb b/test/encryption_test.rb index 6fd63950..05612817 100644 --- a/test/encryption_test.rb +++ b/test/encryption_test.rb @@ -16,7 +16,7 @@ def teardown end def test_encrypt - content = File.open(INPUT_FILE1, 'r').read + content = File.read(INPUT_FILE1) test_filename = 'top_secret_file.txt' password = 'swordfish' @@ -58,11 +58,11 @@ def test_decrypt entry = zis.get_next_entry assert_equal 'file1.txt', entry.name assert_equal 1_327, entry.size - assert_equal ::File.open(INPUT_FILE1, 'r').read, zis.read + assert_equal ::File.read(INPUT_FILE1), zis.read entry = zis.get_next_entry assert_equal 'file2.txt', entry.name assert_equal 41_234, entry.size - assert_equal ::File.open(INPUT_FILE2, 'r').read, zis.read + assert_equal ::File.read(INPUT_FILE2), zis.read end end end diff --git a/test/file_extract_directory_test.rb b/test/file_extract_directory_test.rb index 17d0995a..8c70d759 100644 --- a/test/file_extract_directory_test.rb +++ b/test/file_extract_directory_test.rb @@ -22,7 +22,7 @@ def setup super Dir.rmdir(TEST_OUT_NAME) if File.directory? TEST_OUT_NAME - File.delete(TEST_OUT_NAME) if File.exist? TEST_OUT_NAME + FileUtils.rm_f(TEST_OUT_NAME) end def test_extract_directory diff --git a/test/file_extract_test.rb b/test/file_extract_test.rb index 77da4a6d..9e353d1c 100644 --- a/test/file_extract_test.rb +++ b/test/file_extract_test.rb @@ -38,7 +38,7 @@ def test_extract def test_extract_exists text = 'written text' - ::File.open(EXTRACTED_FILENAME, 'w') { |f| f.write(text) } + ::File.write(EXTRACTED_FILENAME, text) assert_raises(::Zip::DestinationExistsError) do ::Zip::File.open(TEST_ZIP.zip_name) do |zf| @@ -53,7 +53,7 @@ def test_extract_exists def test_extract_exists_overwrite text = 'written text' - ::File.open(EXTRACTED_FILENAME, 'w') { |f| f.write(text) } + ::File.write(EXTRACTED_FILENAME, text) called_correctly = false ::Zip::File.open(TEST_ZIP.zip_name) do |zf| @@ -123,9 +123,7 @@ def test_extract_incorrect_size assert data.include?(true_size_bytes) data.gsub! true_size_bytes, fake_size_bytes - File.open(fake_zip, 'wb') do |file| - file.write data - end + File.binwrite(fake_zip, data) Dir.chdir tmp do ::Zip::File.open(fake_zip) do |zf| @@ -187,9 +185,7 @@ def test_extract_incorrect_size_zip64 assert data.include?(true_size_bytes) data.gsub! true_size_bytes, fake_size_bytes - File.open(fake_zip, 'wb') do |file| - file.write data - end + File.binwrite(fake_zip, data) Dir.chdir tmp do ::Zip::File.open(fake_zip) do |zf| diff --git a/test/file_split_test.rb b/test/file_split_test.rb index 0e54cc99..859bf935 100644 --- a/test/file_split_test.rb +++ b/test/file_split_test.rb @@ -15,10 +15,10 @@ def setup def teardown File.delete(TEST_ZIP.zip_name) - File.delete(UNSPLITTED_FILENAME) if File.exist?(UNSPLITTED_FILENAME) + FileUtils.rm_f(UNSPLITTED_FILENAME) Dir["#{TEST_ZIP.zip_name}.*"].each do |zip_file_name| - File.delete(zip_file_name) if File.exist?(zip_file_name) + FileUtils.rm_f(zip_file_name) end end @@ -33,7 +33,7 @@ def test_split return if result.nil? - Dir["#{TEST_ZIP.zip_name}.*"].sort.each_with_index do |zip_file_name, index| + Dir["#{TEST_ZIP.zip_name}.*"].each_with_index do |zip_file_name, index| File.open(zip_file_name, 'rb') do |zip_file| zip_file.read([::Zip::SPLIT_FILE_SIGNATURE].pack('V').size) if index.zero? File.open(UNSPLITTED_FILENAME, 'ab') do |file| diff --git a/test/file_test.rb b/test/file_test.rb index b6dd766f..b09f78bd 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -22,7 +22,7 @@ def test_create_from_scratch_to_buffer zf.comment = comment end - ::File.open(EMPTY_FILENAME, 'wb') { |file| file.write buffer.string } + ::File.binwrite(EMPTY_FILENAME, buffer.string) zf_read = ::Zip::File.new(EMPTY_FILENAME) assert_equal(comment, zf_read.comment) @@ -105,7 +105,7 @@ def test_get_output_stream assert_equal(entry.time, custom_entry_args[:time]) zf.get_output_stream('entry.bin') do |os| - os.write(::File.open('test/data/generated/5entry.zip', 'rb').read) + os.write(::File.binread('test/data/generated/5entry.zip')) end end @@ -113,7 +113,7 @@ def test_get_output_stream assert_equal(count + 3, zf.size) assert_equal('Putting stuff in new_entry.txt', zf.read('new_entry.txt')) assert_equal('Putting stuff in data/generated/empty.txt', zf.read('test/data/generated/empty.txt')) - assert_equal(File.open('test/data/generated/5entry.zip', 'rb').read, zf.read('entry.bin')) + assert_equal(File.binread('test/data/generated/5entry.zip'), zf.read('entry.bin')) end end @@ -693,7 +693,7 @@ def test_write_buffer old_name = zf.entries.first zf.rename(old_name, new_name) buffer = zf.write_buffer(::StringIO.new) - File.open(TEST_ZIP.zip_name, 'wb') { |f| f.write buffer.string } + File.binwrite(TEST_ZIP.zip_name, buffer.string) zf_read = ::Zip::File.new(TEST_ZIP.zip_name) refute_nil(zf_read.entries.detect { |e| e.name == new_name }) assert_nil(zf_read.entries.detect { |e| e.name == old_name }) diff --git a/test/filesystem/file_nonmutating_test.rb b/test/filesystem/file_nonmutating_test.rb index e6b1f132..46d700b0 100644 --- a/test/filesystem/file_nonmutating_test.rb +++ b/test/filesystem/file_nonmutating_test.rb @@ -406,7 +406,7 @@ def test_foreach zf.file.foreach('test/data/file1.txt') do |l| # Ruby replaces \n with \r\n automatically on windows - newline = Zip::RUNNING_ON_WINDOWS ? l.gsub(/\r\n/, "\n") : l + newline = Zip::RUNNING_ON_WINDOWS ? l.gsub("\r\n", "\n") : l assert_equal(ref[index], newline) index = index.next end @@ -420,7 +420,7 @@ def test_foreach zf.file.foreach('test/data/file1.txt', ' ') do |l| # Ruby replaces \n with \r\n automatically on windows - newline = Zip::RUNNING_ON_WINDOWS ? l.gsub(/\r\n/, "\n") : l + newline = Zip::RUNNING_ON_WINDOWS ? l.gsub("\r\n", "\n") : l assert_equal(ref[index], newline) index = index.next end @@ -486,7 +486,7 @@ def test_readlines zip_file = zf.file.readlines('test/data/file1.txt') # Ruby replaces \n with \r\n automatically on windows - zip_file.each { |l| l.gsub!(/\r\n/, "\n") } if Zip::RUNNING_ON_WINDOWS + zip_file.each { |l| l.gsub!("\r\n", "\n") } if Zip::RUNNING_ON_WINDOWS assert_equal(orig_file, zip_file) end @@ -498,7 +498,7 @@ def test_read # Ruby replaces \n with \r\n automatically on windows zip_file = if Zip::RUNNING_ON_WINDOWS - zf.file.read('test/data/file1.txt').gsub(/\r\n/, "\n") + zf.file.read('test/data/file1.txt').gsub("\r\n", "\n") else zf.file.read('test/data/file1.txt') end diff --git a/test/gentestfiles.rb b/test/gentestfiles.rb index 50820427..75ce4d8f 100755 --- a/test/gentestfiles.rb +++ b/test/gentestfiles.rb @@ -20,7 +20,7 @@ class TestFiles class << self def create_test_files - Dir.mkdir 'test/data/generated' unless Dir.exist?('test/data/generated') + FileUtils.mkdir_p 'test/data/generated' ASCII_TEST_FILES.each_with_index do |filename, index| create_random_ascii(filename, 1E4 * (index + 1)) diff --git a/test/output_stream_test.rb b/test/output_stream_test.rb index b87f64fc..16254f13 100644 --- a/test/output_stream_test.rb +++ b/test/output_stream_test.rb @@ -29,7 +29,7 @@ def test_write_buffer zos.comment = TEST_ZIP.comment write_test_zip(zos) end - File.open(TEST_ZIP.zip_name, 'wb') { |f| f.write buffer.string } + File.binwrite(TEST_ZIP.zip_name, buffer.string) assert_test_zip_contents(TEST_ZIP) end @@ -50,7 +50,7 @@ def test_write_buffer_with_temp_file end tmp_file.rewind - File.open(TEST_ZIP.zip_name, 'wb') { |f| f.write(tmp_file.read) } + File.binwrite(TEST_ZIP.zip_name, tmp_file.read) tmp_file.unlink assert_test_zip_contents(TEST_ZIP) @@ -75,7 +75,7 @@ def test_write_buffer_with_default_io zos.comment = TEST_ZIP.comment write_test_zip(zos) end - File.open(TEST_ZIP.zip_name, 'wb') { |f| f.write buffer.string } + File.binwrite(TEST_ZIP.zip_name, buffer.string) assert_test_zip_contents(TEST_ZIP) end diff --git a/test/settings_test.rb b/test/settings_test.rb index 3bf66e4e..9e280aab 100644 --- a/test/settings_test.rb +++ b/test/settings_test.rb @@ -12,7 +12,7 @@ def setup super Dir.rmdir(TEST_OUT_NAME) if File.directory? TEST_OUT_NAME - File.delete(TEST_OUT_NAME) if File.exist? TEST_OUT_NAME + FileUtils.rm_f(TEST_OUT_NAME) end def teardown diff --git a/test/stored_support_test.rb b/test/stored_support_test.rb index 4e0cdad6..e1cd1813 100644 --- a/test/stored_support_test.rb +++ b/test/stored_support_test.rb @@ -14,11 +14,11 @@ def test_read entry = zis.get_next_entry assert_equal 'file1.txt', entry.name assert_equal 1_327, entry.size - assert_equal ::File.open(INPUT_FILE1, 'r').read, zis.read + assert_equal ::File.read(INPUT_FILE1), zis.read entry = zis.get_next_entry assert_equal 'file2.txt', entry.name assert_equal 41_234, entry.size - assert_equal ::File.open(INPUT_FILE2, 'r').read, zis.read + assert_equal ::File.read(INPUT_FILE2), zis.read end end @@ -29,11 +29,11 @@ def test_encrypted_read entry = zis.get_next_entry assert_equal 'file1.txt', entry.name assert_equal 1_327, entry.size - assert_equal ::File.open(INPUT_FILE1, 'r').read, zis.read + assert_equal ::File.read(INPUT_FILE1), zis.read entry = zis.get_next_entry assert_equal 'file2.txt', entry.name assert_equal 41_234, entry.size - assert_equal ::File.open(INPUT_FILE2, 'r').read, zis.read + assert_equal ::File.read(INPUT_FILE2), zis.read end end end diff --git a/test/test_helper.rb b/test/test_helper.rb index c3ab2bb7..8f665b1c 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -12,7 +12,7 @@ TestFiles.create_test_files TestZipFile.create_test_zips -::MiniTest.after_run do +MiniTest.after_run do FileUtils.rm_rf('test/data/generated') end @@ -137,7 +137,7 @@ module CommonZipFileFixture TEST_ZIP.zip_name = 'test/data/generated/5entry_copy.zip' def setup - File.delete(EMPTY_FILENAME) if File.exist?(EMPTY_FILENAME) + FileUtils.rm_f(EMPTY_FILENAME) FileUtils.cp(TestZipFile::TEST_ZIP2.zip_name, TEST_ZIP.zip_name) end end From 04376dc2bc41e06bfa1dab943f991e332b403554 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Fri, 1 Mar 2024 22:28:41 +0000 Subject: [PATCH 445/469] Update GitHub actions to Node.js 20 versions. --- .github/workflows/lint.yml | 2 +- .github/workflows/tests.yml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0ba0e48c..b777aaa8 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout rubyzip code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install and set up ruby uses: ruby/setup-ruby@v1 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 46117a01..024cdd7f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,7 +19,7 @@ jobs: continue-on-error: ${{ endsWith(matrix.ruby, 'head') || matrix.os == 'windows' }} steps: - name: Checkout rubyzip code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install and set up ruby uses: ruby/setup-ruby@v1 @@ -37,7 +37,7 @@ jobs: - name: Coveralls if: matrix.os == 'ubuntu' && !endsWith(matrix.ruby, 'head') - uses: coverallsapp/github-action@master + uses: coverallsapp/github-action@v2 with: github-token: ${{ secrets.github_token }} flag-name: ${{ matrix.ruby }} @@ -53,7 +53,7 @@ jobs: continue-on-error: true steps: - name: Checkout rubyzip code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install and set up ruby uses: ruby/setup-ruby@v1 @@ -72,7 +72,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Coveralls Finished - uses: coverallsapp/github-action@master + uses: coverallsapp/github-action@v2 with: github-token: ${{ secrets.github_token }} parallel-finished: true From 9cfa01a479e765920c60dae65346e84287f04ffc Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 2 Mar 2024 14:51:25 +0000 Subject: [PATCH 446/469] Update note about minimum Ruby version in the README. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index de6db5e6..f555b77b 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ The public API of some classes has been modernized to use named parameters for o ## Requirements -Version 3.x requires at least Ruby 2.5. +Version 3.x requires at least Ruby 3.0. Version 2.x requires at least Ruby 2.4, and is known to work on Ruby 3.1. From e83bec471bedcb3308ca38a29b6a599933ad46ac Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 2 Mar 2024 14:52:24 +0000 Subject: [PATCH 447/469] Update API documentation. Mostly move things out of the public API by removing them from the docs, but also add and correct docs where appropriate. --- lib/zip.rb | 5 +- lib/zip/constants.rb | 4 ++ lib/zip/entry.rb | 106 +++++++++++++++++++++++---------------- lib/zip/errors.rb | 21 ++++++++ lib/zip/file.rb | 3 +- lib/zip/filesystem.rb | 2 +- lib/zip/input_stream.rb | 21 ++++---- lib/zip/output_stream.rb | 12 ++--- lib/zip/version.rb | 2 +- 9 files changed, 113 insertions(+), 63 deletions(-) diff --git a/lib/zip.rb b/lib/zip.rb index 5d421226..f85e7fb0 100644 --- a/lib/zip.rb +++ b/lib/zip.rb @@ -57,9 +57,9 @@ module Zip restore_ownership: false, restore_permissions: true, restore_times: true - }.freeze + }.freeze # :nodoc: - def reset! + def reset! # :nodoc: @_ran_once = false @unicode_names = false @on_exists_proc = false @@ -73,6 +73,7 @@ def reset! @validate_entry_sizes = true end + # Set options for RubyZip in one block. def setup yield self unless @_ran_once @_ran_once = true diff --git a/lib/zip/constants.rb b/lib/zip/constants.rb index d0c5927e..2fcaff46 100644 --- a/lib/zip/constants.rb +++ b/lib/zip/constants.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true module Zip + # :stopdoc: + RUNNING_ON_WINDOWS = RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/i CENTRAL_DIRECTORY_ENTRY_SIGNATURE = 0x02014b50 @@ -116,4 +118,6 @@ module Zip COMPRESSION_METHOD_PPMD => 'PPMd version I, Rev 1', COMPRESSION_METHOD_AES => 'AES encryption' }.freeze + + # :startdoc: end diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 18959233..fe3b9f11 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -9,16 +9,19 @@ module Zip class Entry include Dirtyable + # Constant used to specify that the entry is stored (i.e., not compressed). STORED = ::Zip::COMPRESSION_METHOD_STORE + + # Constant used to specify that the entry is deflated (i.e., compressed). DEFLATED = ::Zip::COMPRESSION_METHOD_DEFLATE # Language encoding flag (EFS) bit - EFS = 0b100000000000 + EFS = 0b100000000000 # :nodoc: # Compression level flags (used as part of the gp flags). - COMPRESSION_LEVEL_SUPERFAST_GPFLAG = 0b110 - COMPRESSION_LEVEL_FAST_GPFLAG = 0b100 - COMPRESSION_LEVEL_MAX_GPFLAG = 0b010 + COMPRESSION_LEVEL_SUPERFAST_GPFLAG = 0b110 # :nodoc: + COMPRESSION_LEVEL_FAST_GPFLAG = 0b100 # :nodoc: + COMPRESSION_LEVEL_MAX_GPFLAG = 0b010 # :nodoc: attr_accessor :comment, :compressed_size, :follow_symlinks, :name, :restore_ownership, :restore_permissions, :restore_times, @@ -35,7 +38,7 @@ class Entry :fstype=, :gp_flags=, :name=, :size=, :unix_gid=, :unix_perms=, :unix_uid= - def set_default_vars_values + def set_default_vars_values # :nodoc: @local_header_offset = 0 @local_header_size = nil # not known until local entry is created or read @internal_file_attributes = 1 @@ -63,11 +66,12 @@ def set_default_vars_values @unix_perms = nil end - def check_name(name) + def check_name(name) # :nodoc: raise EntryNameError, name if name.start_with?('/') raise EntryNameError if name.length > 65_535 end + # Create a new Zip::Entry. def initialize( zipfile = '', name = '', comment: '', size: nil, compressed_size: 0, crc: 0, @@ -103,18 +107,23 @@ def initialize( set_compression_level_flags end + # Is this entry encrypted? def encrypted? gp_flags & 1 == 1 end - def incomplete? + def incomplete? # :nodoc: gp_flags & 8 == 8 end + # The uncompressed size of the entry. def size @size || 0 end + # Get a timestamp component of this entry. + # + # Returns modification time by default. def time(component: :mtime) time = if @extra['UniversalTime'] @@ -130,14 +139,19 @@ def time(component: :mtime) alias mtime time + # Get the last access time of this entry, if available. def atime time(component: :atime) end + # Get the creation time of this entry, if available. def ctime time(component: :ctime) end + # Set a timestamp component of this entry. + # + # Sets modification time by default. def time=(value, component: :mtime) @dirty = true unless @extra.member?('UniversalTime') || @extra.member?('NTFS') @@ -152,30 +166,38 @@ def time=(value, component: :mtime) alias mtime= time= + # Set the last access time of this entry. def atime=(value) send(:time=, value, component: :atime) end + # Set the creation time of this entry. def ctime=(value) send(:time=, value, component: :ctime) end + # Return the compression method for this entry. + # + # Returns STORED if the entry is a directory or if the compression + # level is 0. def compression_method return STORED if ftype == :directory || @compression_level == 0 @compression_method end + # Set the compression method for this entry. def compression_method=(method) @dirty = true @compression_method = (ftype == :directory ? STORED : method) end + # Does this entry use the ZIP64 extensions? def zip64? !@extra['Zip64'].nil? end - def file_type_is?(type) + def file_type_is?(type) # :nodoc: ftype == type end @@ -190,14 +212,14 @@ def ftype # :nodoc: end end - def name_is_directory? # :nodoc:all + def name_is_directory? # :nodoc: @name.end_with?('/') end # Is the name a relative path, free of `..` patterns that could lead to # path traversal attacks? This does NOT handle symlinks; if the path # contains symlinks, this check is NOT enough to guarantee safety. - def name_safe? + def name_safe? # :nodoc: cleanpath = Pathname.new(@name).cleanpath return false unless cleanpath.relative? @@ -207,29 +229,29 @@ def name_safe? ::File.absolute_path(cleanpath.to_s, root).match?(/([A-Z]:)?#{naive}/i) end - def local_entry_offset # :nodoc:all + def local_entry_offset # :nodoc: local_header_offset + @local_header_size end - def name_size + def name_size # :nodoc: @name ? @name.bytesize : 0 end - def extra_size + def extra_size # :nodoc: @extra ? @extra.local_size : 0 end - def comment_size + def comment_size # :nodoc: @comment ? @comment.bytesize : 0 end - def calculate_local_header_size # :nodoc:all + def calculate_local_header_size # :nodoc: LOCAL_ENTRY_STATIC_HEADER_LENGTH + name_size + extra_size end # check before rewriting an entry (after file sizes are known) # that we didn't change the header size (and thus clobber file data or something) - def verify_local_header_size! + def verify_local_header_size! # :nodoc: return if @local_header_size.nil? new_size = calculate_local_header_size @@ -239,12 +261,12 @@ def verify_local_header_size! "Local header size changed (#{@local_header_size} -> #{new_size})" end - def cdir_header_size # :nodoc:all + def cdir_header_size # :nodoc: CDIR_ENTRY_STATIC_HEADER_LENGTH + name_size + (@extra ? @extra.c_dir_size : 0) + comment_size end - def next_header_offset # :nodoc:all + def next_header_offset # :nodoc: local_entry_offset + compressed_size end @@ -270,12 +292,12 @@ def extract(entry_path = @name, destination_directory: '.', &block) self end - def to_s + def to_s # :nodoc: @name end class << self - def read_c_dir_entry(io) # :nodoc:all + def read_c_dir_entry(io) # :nodoc: path = if io.respond_to?(:path) io.path else @@ -288,7 +310,7 @@ def read_c_dir_entry(io) # :nodoc:all nil end - def read_local_entry(io) + def read_local_entry(io) # :nodoc: entry = new(io) entry.read_local_entry(io) entry @@ -299,7 +321,7 @@ def read_local_entry(io) end end - def unpack_local_entry(buf) + def unpack_local_entry(buf) # :nodoc: @header_signature, @version, @fstype, @@ -314,7 +336,7 @@ def unpack_local_entry(buf) @extra_length = buf.unpack('VCCvvvvVVVvv') end - def read_local_entry(io) # :nodoc:all + def read_local_entry(io) # :nodoc: @dirty = false # No changes at this point. @local_header_offset = io.tell @@ -356,7 +378,7 @@ def read_local_entry(io) # :nodoc:all @local_header_size = calculate_local_header_size end - def pack_local_entry + def pack_local_entry # :nodoc: zip64 = @extra['Zip64'] [::Zip::LOCAL_ENTRY_SIGNATURE, @version_needed_to_extract, # version needed to extract @@ -371,7 +393,7 @@ def pack_local_entry @extra ? @extra.local_size : 0].pack('VvvvvvVVVvv') end - def write_local_entry(io, rewrite: false) # :nodoc:all + def write_local_entry(io, rewrite: false) # :nodoc: prep_local_zip64_extra verify_local_header_size! if rewrite @local_header_offset = io.tell @@ -383,7 +405,7 @@ def write_local_entry(io, rewrite: false) # :nodoc:all @local_header_size = io.tell - @local_header_offset end - def unpack_c_dir_entry(buf) + def unpack_c_dir_entry(buf) # :nodoc: @header_signature, @version, # version of encoding software @fstype, # filesystem type @@ -407,7 +429,7 @@ def unpack_c_dir_entry(buf) @comment = buf.unpack('VCCvvvvvVVVvvvvvVV') end - def set_ftype_from_c_dir_entry + def set_ftype_from_c_dir_entry # :nodoc: @ftype = case @fstype when ::Zip::FSTYPE_UNIX @unix_perms = (@external_file_attributes >> 16) & 0o7777 @@ -437,25 +459,25 @@ def set_ftype_from_c_dir_entry end end - def check_c_dir_entry_static_header_length(buf) + def check_c_dir_entry_static_header_length(buf) # :nodoc: return unless buf.nil? || buf.bytesize != ::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH raise Error, 'Premature end of file. Not enough data for zip cdir entry header' end - def check_c_dir_entry_signature + def check_c_dir_entry_signature # :nodoc: return if @header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE raise Error, "Zip local header magic not found at location '#{local_header_offset}'" end - def check_c_dir_entry_comment_size + def check_c_dir_entry_comment_size # :nodoc: return if @comment && @comment.bytesize == @comment_length raise ::Zip::Error, 'Truncated cdir zip entry header' end - def read_extra_field(buf, local: false) + def read_extra_field(buf, local: false) # :nodoc: if @extra.kind_of?(::Zip::ExtraField) @extra.merge(buf, local: local) if buf else @@ -463,7 +485,7 @@ def read_extra_field(buf, local: false) end end - def read_c_dir_entry(io) # :nodoc:all + def read_c_dir_entry(io) # :nodoc: @dirty = false # No changes at this point. static_sized_fields_buf = io.read(::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH) check_c_dir_entry_static_header_length(static_sized_fields_buf) @@ -503,7 +525,7 @@ def get_extra_attributes_from_path(path) # :nodoc: end # rubocop:disable Style/GuardClause - def set_unix_attributes_on_path(dest_path) + def set_unix_attributes_on_path(dest_path) # :nodoc: # Ignore setuid/setgid bits by default. Honour if @restore_ownership. unix_perms_mask = (@restore_ownership ? 0o7777 : 0o1777) if @restore_permissions && @unix_perms @@ -529,7 +551,7 @@ def set_extra_attributes_on_path(dest_path) # :nodoc: ::FileUtils.touch(dest_path, mtime: time) if @restore_times end - def pack_c_dir_entry + def pack_c_dir_entry # :nodoc: zip64 = @extra['Zip64'] [ @header_signature, @@ -556,7 +578,7 @@ def pack_c_dir_entry ].pack('VCCvvvvvVVVvvvvvVV') end - def write_c_dir_entry(io) # :nodoc:all + def write_c_dir_entry(io) # :nodoc: prep_cdir_zip64_extra case @fstype @@ -585,7 +607,7 @@ def write_c_dir_entry(io) # :nodoc:all io << @comment end - def ==(other) + def ==(other) # :nodoc: return false unless other.class == self.class # Compares contents of local entry and exposed fields @@ -594,7 +616,7 @@ def ==(other) end end - def <=>(other) + def <=>(other) # :nodoc: to_s <=> other.to_s end @@ -661,7 +683,7 @@ def gather_fileinfo_from_srcpath(src_path) # :nodoc: get_extra_attributes_from_path(@filepath) end - def write_to_zip_output_stream(zip_output_stream) # :nodoc:all + def write_to_zip_output_stream(zip_output_stream) # :nodoc: if ftype == :directory zip_output_stream.put_next_entry(self) elsif @filepath @@ -674,13 +696,13 @@ def write_to_zip_output_stream(zip_output_stream) # :nodoc:all end end - def parent_as_string + def parent_as_string # :nodoc: entry_name = name.chomp('/') slash_index = entry_name.rindex('/') slash_index ? entry_name.slice(0, slash_index + 1) : nil end - def get_raw_input_stream(&block) + def get_raw_input_stream(&block) # :nodoc: if @zipfile.respond_to?(:seek) && @zipfile.respond_to?(:read) yield @zipfile else @@ -688,7 +710,7 @@ def get_raw_input_stream(&block) end end - def clean_up + def clean_up # :nodoc: @dirty = false # Any changes are written at this point. end @@ -749,7 +771,7 @@ def create_symlink(dest_path) # apply missing data from the zip64 extra information field, if present # (required when file sizes exceed 2**32, but can be used for all files) - def parse_zip64_extra(for_local_header) # :nodoc:all + def parse_zip64_extra(for_local_header) # :nodoc: return unless zip64? if for_local_header diff --git a/lib/zip/errors.rb b/lib/zip/errors.rb index 5b75e9a6..f172a77f 100644 --- a/lib/zip/errors.rb +++ b/lib/zip/errors.rb @@ -7,13 +7,17 @@ class Error < StandardError; end # Error raised if an unsupported compression method is used. class CompressionMethodError < Error + # The compression method that has caused this error. attr_reader :compression_method + # Create a new CompressionMethodError with the specified incorrect + # compression method. def initialize(method) super() @compression_method = method end + # The message returned by this error. def message "Unsupported compression method: #{COMPRESSION_METHODS[@compression_method]}." end @@ -21,13 +25,17 @@ def message # Error raised if there is a problem while decompressing an archive entry. class DecompressionError < Error + # The error from the underlying Zlib library that caused this error. attr_reader :zlib_error + # Create a new DecompressionError with the specified underlying Zlib + # error. def initialize(zlib_error) super() @zlib_error = zlib_error end + # The message returned by this error. def message "Zlib error ('#{@zlib_error.message}') while inflating." end @@ -36,11 +44,13 @@ def message # Error raised when trying to extract an archive entry over an # existing file. class DestinationExistsError < Error + # Create a new DestinationExistsError with the clashing destination. def initialize(destination) super() @destination = destination end + # The message returned by this error. def message "Cannot create file or directory '#{@destination}'. " \ 'A file already exists with that name.' @@ -50,12 +60,14 @@ def message # Error raised when trying to add an entry to an archive where the # entry name already exists. class EntryExistsError < Error + # Create a new EntryExistsError with the specified source and name. def initialize(source, name) super() @source = source @name = name end + # The message returned by this error. def message "'#{@source}' failed. Entry #{@name} already exists." end @@ -63,11 +75,13 @@ def message # Error raised when an entry name is invalid. class EntryNameError < Error + # Create a new EntryNameError with the specified name. def initialize(name = nil) super() @name = name end + # The message returned by this error. def message if @name.nil? 'Illegal entry name. Names must have fewer than 65,536 characters.' @@ -80,13 +94,16 @@ def message # Error raised if an entry is larger on extraction than it is advertised # to be. class EntrySizeError < Error + # The entry that has caused this error. attr_reader :entry + # Create a new EntrySizeError with the specified entry. def initialize(entry) super() @entry = entry end + # The message returned by this error. def message "Entry '#{@entry.name}' should be #{@entry.size}B, but is larger when inflated." end @@ -95,6 +112,7 @@ def message # Error raised if a split archive is read. Rubyzip does not support reading # split archives. class SplitArchiveError < Error + # The message returned by this error. def message 'Rubyzip cannot extract from split archives at this time.' end @@ -102,13 +120,16 @@ def message # Error raised if there is not enough metadata for the entry to be streamed. class StreamingError < Error + # The entry that has caused this error. attr_reader :entry + # Create a new StreamingError with the specified entry. def initialize(entry) super() @entry = entry end + # The message returned by this error. def message "The local header of this entry ('#{@entry.name}') does not contain " \ 'the correct metadata for `Zip::InputStream` to be able to ' \ diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 85a75035..754b9575 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -52,8 +52,9 @@ class File extend Forwardable extend FileSplit - IO_METHODS = [:tell, :seek, :read, :eof, :close].freeze + IO_METHODS = [:tell, :seek, :read, :eof, :close].freeze # :nodoc: + # The name of this zip archive. attr_reader :name # default -> false. diff --git a/lib/zip/filesystem.rb b/lib/zip/filesystem.rb index 5a65c974..55369e92 100644 --- a/lib/zip/filesystem.rb +++ b/lib/zip/filesystem.rb @@ -64,7 +64,7 @@ def file end end - class File + class File # :nodoc: include FileSystem end end diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index 56ecf4f9..7cb18ca5 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +## module Zip # InputStream is the basic class for reading zip entries in a # zip file. It is possible to create a InputStream object directly, @@ -39,9 +40,8 @@ module Zip # # java.util.zip.ZipInputStream is the original inspiration for this # class. - class InputStream - CHUNK_SIZE = 32_768 + CHUNK_SIZE = 32_768 # :nodoc: include ::Zip::IOExtras::AbstractInputStream @@ -60,6 +60,7 @@ def initialize(context, offset: 0, decrypter: nil) @complete_entry = nil end + # Close this InputStream. All further IO will raise an IOError. def close @archive_io.close end @@ -78,7 +79,7 @@ def get_next_entry open_entry end - # Rewinds the stream to the beginning of the current entry + # Rewinds the stream to the beginning of the current entry. def rewind return if @current_entry.nil? @@ -115,7 +116,7 @@ def open(filename_or_io, offset: 0, decrypter: nil) end end - def open_buffer(filename_or_io, offset: 0) + def open_buffer(filename_or_io, offset: 0) # :nodoc: warn 'open_buffer is deprecated!!! Use open instead!' ::Zip::InputStream.open(filename_or_io, offset: offset) end @@ -123,7 +124,7 @@ def open_buffer(filename_or_io, offset: 0) protected - def get_io(io_or_file, offset = 0) + def get_io(io_or_file, offset = 0) # :nodoc: if io_or_file.respond_to?(:seek) io = io_or_file.dup io.seek(offset, ::IO::SEEK_SET) @@ -135,7 +136,7 @@ def get_io(io_or_file, offset = 0) end end - def open_entry + def open_entry # :nodoc: @current_entry = ::Zip::Entry.read_local_entry(@archive_io) return if @current_entry.nil? @@ -154,14 +155,14 @@ def open_entry @current_entry end - def get_decrypted_io + def get_decrypted_io # :nodoc: header = @archive_io.read(@decrypter.header_bytesize) @decrypter.reset!(header) ::Zip::DecryptedIo.new(@archive_io, @decrypter) end - def get_decompressor + def get_decompressor # :nodoc: return ::Zip::NullDecompressor if @current_entry.nil? decompressed_size = @@ -182,11 +183,11 @@ def get_decompressor decompressor_class.new(@decrypted_io, decompressed_size) end - def produce_input + def produce_input # :nodoc: @decompressor.read(CHUNK_SIZE) end - def input_finished? + def input_finished? # :nodoc: @decompressor.eof end end diff --git a/lib/zip/output_stream.rb b/lib/zip/output_stream.rb index 095126b5..0609f89f 100644 --- a/lib/zip/output_stream.rb +++ b/lib/zip/output_stream.rb @@ -2,6 +2,7 @@ require 'forwardable' +## module Zip # ZipOutputStream is the basic class for writing zip files. It is # possible to create a ZipOutputStream object directly, passing @@ -20,7 +21,6 @@ module Zip # # java.util.zip.ZipOutputStream is the original inspiration for this # class. - class OutputStream extend Forwardable include ::Zip::IOExtras::AbstractOutputStream @@ -47,10 +47,10 @@ def initialize(file_name, stream: false, encrypter: nil) @current_entry = nil end - # Same as #initialize but if a block is passed the opened - # stream is passed to the block and closed when the block - # returns. class << self + # Same as #initialize but if a block is passed the opened + # stream is passed to the block and closed when the block + # returns. def open(file_name, encrypter: nil) return new(file_name) unless block_given? @@ -114,7 +114,7 @@ def put_next_entry( @current_entry = new_entry end - def copy_raw_entry(entry) + def copy_raw_entry(entry) # :nodoc: entry = entry.dup raise Error, 'zip stream is closed' if @closed raise Error, 'entry is not a ZipEntry' unless entry.kind_of?(Entry) @@ -185,7 +185,7 @@ def update_local_headers protected - def finish + def finish # :nodoc: @compressor.finish end diff --git a/lib/zip/version.rb b/lib/zip/version.rb index dd203608..8878fc45 100644 --- a/lib/zip/version.rb +++ b/lib/zip/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Zip - VERSION = '3.0.0.alpha' + VERSION = '3.0.0.alpha' # :nodoc: end From bfc9324a75b26b583c6d4337db85f4c40a3a8c84 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 2 Mar 2024 14:55:46 +0000 Subject: [PATCH 448/469] Remove deprecated InputStream::open_buffer method. --- lib/zip/input_stream.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index 7cb18ca5..defccabc 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -115,11 +115,6 @@ def open(filename_or_io, offset: 0, decrypter: nil) zio.close if zio end end - - def open_buffer(filename_or_io, offset: 0) # :nodoc: - warn 'open_buffer is deprecated!!! Use open instead!' - ::Zip::InputStream.open(filename_or_io, offset: offset) - end end protected From c6229cc8c20f107a8d7afa8215f468d41d25ae89 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 5 Mar 2024 20:35:56 +0000 Subject: [PATCH 449/469] Remove TODO file. Out of date and we use GitHub issues now anyway. --- TODO | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 TODO diff --git a/TODO b/TODO deleted file mode 100644 index 16b9a2e7..00000000 --- a/TODO +++ /dev/null @@ -1,15 +0,0 @@ - -* ZipInputStream: Support zip-files with trailing data descriptors -* Adjust rdoc stylesheet to advertise inherited methods if possible -* Suggestion: Add ZipFile/ZipInputStream example that demonstrates extracting all entries. -* Suggestion: ZipFile#extract destination should default to "." -* Suggestion: ZipEntry should have extract(), get_input_stream() methods etc -* (is buffering used anywhere with write?) -* Inflater.sysread should pass the buffer to produce_input. -* Implement ZipFsDir.glob -* ZipFile.checkIntegrity method -* non-MSDOS permission attributes -** See mail from Ned Konz to ruby-talk subj. "Re: SV: [ANN] Archive 0.2" -* Packager version, required unpacker version in zip headers -** See mail from Ned Konz to ruby-talk subj. "Re: SV: [ANN] Archive 0.2" -* implement storing attributes and ownership information From 0e4fc83b2516e8ed093348727ab45527e3016944 Mon Sep 17 00:00:00 2001 From: Oleksii Leonov Date: Sat, 13 Jan 2024 22:47:56 +0000 Subject: [PATCH 450/469] Add LICENSE file --- LICENSE.md | 24 ++++++++++++++++++++++++ rubyzip.gemspec | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 LICENSE.md diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..16e431c1 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,24 @@ +BSD 2-Clause License + +Copyright (c) 2002-2024, The Rubyzip Developers + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/rubyzip.gemspec b/rubyzip.gemspec index f9d85f1c..7392d538 100644 --- a/rubyzip.gemspec +++ b/rubyzip.gemspec @@ -13,7 +13,7 @@ Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.summary = 'rubyzip is a ruby module for reading and writing zip files' s.files = Dir.glob('{samples,lib}/**/*.rb') + - %w[README.md Changelog.md Rakefile rubyzip.gemspec] + %w[LICENSE.md README.md Changelog.md Rakefile rubyzip.gemspec] s.require_paths = ['lib'] s.license = 'BSD-2-Clause' From 73c8e110ed1dbcff08ffa48bb1b094abd0348502 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 6 Mar 2024 20:53:48 +0000 Subject: [PATCH 451/469] Update README with up-to-date licence information. Fixes: #556 --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index f555b77b..700a2dd9 100644 --- a/README.md +++ b/README.md @@ -449,8 +449,7 @@ See https://github.com/rubyzip/rubyzip/graphs/contributors for a comprehensive l ## License -Rubyzip is distributed under the same license as ruby. See -http://www.ruby-lang.org/en/LICENSE.txt +Rubyzip is distributed under the same license as Ruby. In practice this means you can use it under the terms of the Ruby License or the 2-Clause BSD License. See https://www.ruby-lang.org/en/about/license.txt and LICENSE.md for details. ## Research notice Please note that this repository is participating in a study into sustainability From 8afc2514f7e54d5dbdf89363efd89b093e532319 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Thu, 7 Mar 2024 16:57:50 +0000 Subject: [PATCH 452/469] Use explicit named parameters for `File` methods. Stop using implicit options (`**options`). --- lib/zip/file.rb | 46 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 754b9575..5ea27ea2 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -70,28 +70,40 @@ class File # Opens a zip archive. Pass create: true to create # a new archive if it doesn't exist already. - 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) super() - options = DEFAULT_RESTORE_OPTIONS - .merge(compression_level: ::Zip.default_compression) - .merge(options) + @name = path_or_io.respond_to?(:path) ? path_or_io.path : path_or_io @create = create ? true : false # allow any truthy value to mean true initialize_cdir(path_or_io, buffer: buffer) - @restore_ownership = options[:restore_ownership] - @restore_permissions = options[:restore_permissions] - @restore_times = options[:restore_times] - @compression_level = options[:compression_level] + @restore_ownership = restore_ownership + @restore_permissions = restore_permissions + @restore_times = restore_times + @compression_level = compression_level end class << self # Similar to ::new. If a block is passed the Zip::File object is passed # to the block and is automatically closed afterwards, just as with # ruby's builtin File::open method. - def open(file_name, create: false, **options) - zf = ::Zip::File.new(file_name, create: create, **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) + + zf = ::Zip::File.new(file_name, create: create, + restore_ownership: restore_ownership, + restore_permissions: restore_permissions, + restore_times: restore_times, + compression_level: compression_level) + return zf unless block_given? begin @@ -105,7 +117,12 @@ def open(file_name, create: false, **options) # stream, and outputs data to a buffer. # (This can be used to extract data from a # downloaded zip archive without first saving it to disk.) - def open_buffer(io = ::StringIO.new, create: false, **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) + unless IO_METHODS.map { |method| io.respond_to?(method) }.all? || io.kind_of?(String) raise 'Zip::File.open_buffer expects a String or IO-like argument' \ "(responds to #{IO_METHODS.join(', ')}). Found: #{io.class}" @@ -113,7 +130,12 @@ def open_buffer(io = ::StringIO.new, create: false, **options) io = ::StringIO.new(io) if io.kind_of?(::String) - zf = ::Zip::File.new(io, create: create, buffer: true, **options) + zf = ::Zip::File.new(io, create: create, buffer: true, + restore_ownership: restore_ownership, + restore_permissions: restore_permissions, + restore_times: restore_times, + compression_level: compression_level) + return zf unless block_given? yield zf From d53f046bc7960a4913ef028f8881c8e22ae62ca8 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Thu, 7 Mar 2024 21:31:24 +0000 Subject: [PATCH 453/469] Add `Entry#absolute_time?`. This method returns `true` if an entry has timezone information in its timestamps, `false` otherwise. --- lib/zip/entry.rb | 5 +++++ test/entry_test.rb | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index fe3b9f11..9e1a904c 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -176,6 +176,11 @@ def ctime=(value) send(:time=, value, component: :ctime) end + # Does this entry return time fields with accurate timezone information? + def absolute_time? + @extra.member?('UniversalTime') || @extra.member?('NTFS') + end + # Return the compression method for this entry. # # Returns STORED if the entry is a directory or if the compression diff --git a/test/entry_test.rb b/test/entry_test.rb index 0d88c874..b2d4c14e 100644 --- a/test/entry_test.rb +++ b/test/entry_test.rb @@ -340,4 +340,12 @@ def test_ensure_entry_time_set_to_file_mtime entry.gather_fileinfo_from_srcpath('test/data/mimetype') assert_equal(entry.time, File.stat('test/data/mimetype').mtime) end + + def test_absolute_time + entry = ::Zip::Entry.new + refute(entry.absolute_time?) + + entry.time = Time.now + assert(entry.absolute_time?) + end end From 0c0003cfda19e5c3726b29c46d5f75de802af547 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Fri, 8 Mar 2024 21:41:20 +0000 Subject: [PATCH 454/469] Add `DOSTime#absolute_time?`. This method returns `true` if the time instance was created with accurate timezone information. Ultimately, only those times parsed from binary DOS format are missing accurate timezone information, but we need this flag because ruby `Time` objects (from which `DOSTime` is decended) always have a timezone set (usually whatever is local at the time). --- lib/zip/dos_time.rb | 12 +++++- test/dos_time_test.rb | 86 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 test/dos_time_test.rb diff --git a/lib/zip/dos_time.rb b/lib/zip/dos_time.rb index 0d94f21c..ac80fac5 100644 --- a/lib/zip/dos_time.rb +++ b/lib/zip/dos_time.rb @@ -16,6 +16,14 @@ class DOSTime < Time # :nodoc:all # bits 5-8 month (1-12) # bits 9-15 year (four digit year minus 1980) + attr_writer :absolute_time # :nodoc: + + def absolute_time? + # If absolute time is not set, we can assume it is an absolute time + # because times do have timezone information by default. + @absolute_time.nil? ? true : @absolute_time + end + def to_binary_dos_time (sec / 2) + (min << 5) + @@ -53,7 +61,9 @@ def self.parse_binary_dos_format(bin_dos_date, bin_dos_time) month = (0b111100000 & bin_dos_date) >> 5 year = ((0b1111111000000000 & bin_dos_date) >> 9) + 1980 - local(year, month, day, hour, minute, second) + time = local(year, month, day, hour, minute, second) + time.absolute_time = false + time end if defined? JRUBY_VERSION && Gem::Version.new(JRUBY_VERSION) < '9.2.18.0' diff --git a/test/dos_time_test.rb b/test/dos_time_test.rb new file mode 100644 index 00000000..70a38a9f --- /dev/null +++ b/test/dos_time_test.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +require 'test_helper' +require 'zip/dos_time' + +class DOSTimeTest < MiniTest::Test + def setup + @dos_time = Zip::DOSTime.new(2022, 1, 1, 12, 0, 0) + end + + def test_new + dos_time = Zip::DOSTime.new + assert(dos_time.absolute_time?) + + dos_time = Zip::DOSTime.new(2022, 1, 1, 12, 0, 0) + assert(dos_time.absolute_time?) + + dos_time = Zip::DOSTime.new(2022, 1, 1, 12, 0, 0, 0) + assert(dos_time.absolute_time?) + end + + def test_now + dos_time = Zip::DOSTime.now + assert(dos_time.absolute_time?) + end + + def test_utc + dos_time = Zip::DOSTime.utc(2022, 1, 1, 12, 0, 0) + assert(dos_time.absolute_time?) + end + + def test_gm + dos_time = Zip::DOSTime.gm(2022, 1, 1, 12, 0, 0) + assert(dos_time.absolute_time?) + end + + def test_mktime + dos_time = Zip::DOSTime.mktime(2022, 1, 1, 12, 0, 0) + assert(dos_time.absolute_time?) + end + + def test_from_time + time = Time.new(2022, 1, 1, 12, 0, 0) + dos_time = Zip::DOSTime.from_time(time) + assert_equal(@dos_time, dos_time) + assert(dos_time.absolute_time?) + end + + def test_parse_binary_dos_format + bin_dos_date = 0b101010000100001 + bin_dos_time = 0b110000000000000 + dos_time = Zip::DOSTime.parse_binary_dos_format(bin_dos_date, bin_dos_time) + assert_equal(@dos_time, dos_time) + refute(dos_time.absolute_time?) + end + + def test_at + time = Time.at(1_641_038_400) + dos_time = Zip::DOSTime.at(1_641_038_400) + assert_equal(time, dos_time) + assert(dos_time.absolute_time?) + end + + def test_local + dos_time = Zip::DOSTime.local(2022, 1, 1, 12, 0, 0) + assert(dos_time.absolute_time?) + end + + def test_comparison + time = Time.new(2022, 1, 1, 12, 0, 0) + assert_equal(0, @dos_time <=> time) + end + + def test_jruby_cmp + return unless defined? JRUBY_VERSION && Gem::Version.new(JRUBY_VERSION) < '9.2.18.0' + + time = Time.new(2022, 1, 1, 12, 0, 0) + assert(@dos_time == time) + assert(@dos_time <= time) + assert(@dos_time >= time) + + time = Time.new(2022, 1, 1, 12, 1, 1) + assert(time > @dos_time) + assert(@dos_time < time) + end +end From 5c6a7c9ad967b128905b0ecacfd9f5dc1b12a9c7 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Tue, 9 Apr 2024 09:10:04 +0200 Subject: [PATCH 455/469] Fix `File#write_buffer` to always return the given `io` Ref: https://github.com/rubyzip/rubyzip/commit/ef89a62b70d0b0c43a9579d414d5a917fef0cdc3 This fixes a regression in 2.4.rc1. Cherry-picked into 3.0 for consistency. --- lib/zip/file.rb | 2 +- test/file_test.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 5ea27ea2..5c89606b 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -301,7 +301,7 @@ def commit # Write buffer write changes to buffer and return def write_buffer(io = ::StringIO.new) - return unless commit_required? + return io unless commit_required? ::Zip::OutputStream.write_buffer(io) do |zos| @cdir.each { |e| e.write_to_zip_output_stream(zos) } diff --git a/test/file_test.rb b/test/file_test.rb index b09f78bd..8179ce97 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -233,7 +233,7 @@ def test_open_buffer_without_block_write_buffer_does_nothing assert zf.entries.map(&:name).include?('zippedruby1.rb') # Ensure the buffer isn't changed. - zf.write_buffer(string_io) + assert_same(string_io, zf.write_buffer(string_io)) assert_equal(data, string_io.string) end From f81175b3d3b1e1355a3782e5c8eb049ce09a4d66 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 26 Oct 2024 19:16:55 +0100 Subject: [PATCH 456/469] Update Changlog. --- Changelog.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Changelog.md b/Changelog.md index 84b45492..ce1c0b99 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,21 @@ # 3.0.0 (Next) +- Fix `File#write_buffer` to always return the given `io`. +- Add `Entry#absolute_time?` and `DOSTime#absolute_time?` methods. +- Use explicit named parameters for `File` methods. +- Ensure that entries can be extracted safely without path traversal. [#540](https://github.com/rubyzip/rubyzip/issues/540) +- Enable Zip64 by default. +- Rename `GPFBit3Error` to `StreamingError`. +- Ensure that `Entry.ftype` is correct via `InputStream`. [#533](https://github.com/rubyzip/rubyzip/issues/533) +- Add `Entry#zip64?` as a better way detect Zip64 entries. +- Implement `Zip::FileSystem::ZipFsFile#symlink?`. +- Remove `File::add_buffer` from the API. +- Fix `OutputStream#put_next_entry` to preserve `StreamableStream`s. [#503](https://github.com/rubyzip/rubyzip/issues/503) +- Ensure `File.open_buffer` doesn't rewrite unchanged data. +- Add `CentralDirectory#count_entries` and `File::count_entries`. +- Fix reading unknown extra fields. [#505](https://github.com/rubyzip/rubyzip/issues/505) +- Fix reading zip files with max length file comment. [#508](https://github.com/rubyzip/rubyzip/issues/508) +- Fix reading zip64 files with max length file comment. [#509](https://github.com/rubyzip/rubyzip/issues/509) - Don't silently alter zip files opened with `Zip::sort_entries`. [#329](https://github.com/rubyzip/rubyzip/issues/329) - Use named parameters for optional arguments in the public API. - Raise an error if entry names exceed 65,535 characters. [#247](https://github.com/rubyzip/rubyzip/issues/247) @@ -23,6 +39,13 @@ Tooling/internal: +- Only use the Zip64 CDIR end locator if needed. +- Prevent unnecessary Zip64 data being stored. +- Abstract marking various things as 'dirty' into `Dirtyable` for reuse. +- Properly test `File#mkdir`. +- Remove unused private method `File#directory?`. +- Expose the `EntrySet` more cleanly through `CentralDirectory`. +- `Zip::File` no longer subclasses `Zip::CentralDirectory`. - Configure Coveralls to not report a failure on minor decreases of test coverage. [#491](https://github.com/rubyzip/rubyzip/issues/491) - Extract the file splitting code out into its own module. - Refactor, and tidy up, the `Zip::Filesystem` classes for improved maintainability. From 5b0d25e416814beb062c707c1319eb79b9d4272f Mon Sep 17 00:00:00 2001 From: Bastien Date: Fri, 26 Apr 2024 17:16:40 +0200 Subject: [PATCH 457/469] Fix misspell --- lib/zip/crypto/null_encryption.rb | 2 +- lib/zip/crypto/traditional_encryption.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/zip/crypto/null_encryption.rb b/lib/zip/crypto/null_encryption.rb index 2ae48b70..97764b73 100644 --- a/lib/zip/crypto/null_encryption.rb +++ b/lib/zip/crypto/null_encryption.rb @@ -22,7 +22,7 @@ def encrypt(data) data end - def data_descriptor(_crc32, _compressed_size, _uncomprssed_size) + def data_descriptor(_crc32, _compressed_size, _uncompressed_size) '' end diff --git a/lib/zip/crypto/traditional_encryption.rb b/lib/zip/crypto/traditional_encryption.rb index f035895d..dc92c822 100644 --- a/lib/zip/crypto/traditional_encryption.rb +++ b/lib/zip/crypto/traditional_encryption.rb @@ -55,8 +55,8 @@ def encrypt(data) data.unpack('C*').map { |x| encode x }.pack('C*') end - def data_descriptor(crc32, compressed_size, uncomprssed_size) - [0x08074b50, crc32, compressed_size, uncomprssed_size].pack('VVVV') + def data_descriptor(crc32, compressed_size, uncompressed_size) + [0x08074b50, crc32, compressed_size, uncompressed_size].pack('VVVV') end def reset! From d908e72819a552f7235a4a756a56fdc4ed5f55c3 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 4 Jan 2025 16:17:25 +0000 Subject: [PATCH 458/469] Add Ruby 3.4 to the tests. --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 024cdd7f..31564eb8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,7 +8,7 @@ jobs: fail-fast: false matrix: os: [ubuntu] - ruby: ['3.0', '3.1', '3.2', '3.3', head, jruby, jruby-head, truffleruby, truffleruby-head] + ruby: ['3.0', '3.1', '3.2', '3.3', '3.4', head, jruby, jruby-head, truffleruby, truffleruby-head] include: - { os: macos , ruby: '3.0' } - { os: windows, ruby: '3.0' } @@ -48,7 +48,7 @@ jobs: fail-fast: false matrix: os: [ubuntu, macos] - ruby: ['3.1', '3.2', '3.3', head] + ruby: ['3.1', '3.2', '3.3', '3.4', head] runs-on: ${{ matrix.os }}-latest continue-on-error: true steps: From 7412338c2e616680b93b670591d0cd5420e709aa Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 4 Jan 2025 16:18:27 +0000 Subject: [PATCH 459/469] Fix variable name typo in `DecryptedIo`. Fixes: #580 --- lib/zip/crypto/decrypted_io.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zip/crypto/decrypted_io.rb b/lib/zip/crypto/decrypted_io.rb index db844a24..a38004fa 100644 --- a/lib/zip/crypto/decrypted_io.rb +++ b/lib/zip/crypto/decrypted_io.rb @@ -18,7 +18,7 @@ def read(length = nil, outbuf = +'') buffer << produce_input end - outbuf.replace(buffer.slice!(0...(length || output_buffer.bytesize))) + outbuf.replace(buffer.slice!(0...(length || buffer.bytesize))) end private From b3186d7ad19618cdcdde9d52095b8903fe599335 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 4 Jan 2025 16:37:02 +0000 Subject: [PATCH 460/469] Update the version of RubyGems in the actions. --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 31564eb8..e20d5f94 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -25,7 +25,7 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} - rubygems: '3.2.3' + rubygems: '3.5.23' bundler-cache: true - name: Run the tests From 89cdf82a7794dbbd4d4bcfd4726eb42641257d5b Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 5 Jan 2025 19:24:16 +0000 Subject: [PATCH 461/469] Copy the 2.4 branch changelog into the main branch. --- Changelog.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Changelog.md b/Changelog.md index ce1c0b99..ea6e39c1 100644 --- a/Changelog.md +++ b/Changelog.md @@ -61,6 +61,29 @@ Tooling/internal: - Update rubocop again and run it in CI. [#444](https://github.com/rubyzip/rubyzip/pull/444) - Fix a test that was incorrect on big-endian architectures. [#445](https://github.com/rubyzip/rubyzip/pull/445) +# 2.4.1 (2025-01-05) + +*This is a re-release of version 2.4 with a full version number string. We need to move to version 2.4.1 due to the canonical version number 2.4 now being taken in Rubygems.* + +Tooling: + +- Opt-in for MFA requirement explicitly on 2.4 branch. + +# 2.4 (2025-01-04) - Yanked + +*Yanked due to incorrect version number format (2.4 vs 2.4.0).* + +- Ensure compatibility with `--enable-frozen-string-literal`. +- Ensure `File.open_buffer` doesn't rewrite unchanged data. This is a backport of the fix on the 3.x branch. +- Enable use of the version 3 calling style (mainly named parameters) wherever possible, while retaining version 2.x compatibility. +- Add (switchable) warning messages to methods that are changed or removed in version 3.x. + +Tooling: + +- Switch to using GitHub Actions (from Travis). +- Update Rubocop versions and configuration. +- Update actions with latest rubies. + # 2.3.2 (2021-07-05) - A "dummy" release to warn about breaking changes coming in version 3.0. This updated version uses the Gem `post_install_message` instead of printing to `STDERR`. From 0d920d552f809a261c31345b8009752f65a01fdb Mon Sep 17 00:00:00 2001 From: Geremia Taglialatela Date: Tue, 7 Jan 2025 08:40:56 +0100 Subject: [PATCH 462/469] Update README with Ruby version compatibility This commit updates the README to reflect the Ruby versions that are known to work with version 2.x of the library. Specifically, it documents that version 2.x works on Ruby 3.x. Ref: 0001864cfe0a1e76879179dfa1ba7b9e60d5a991 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 700a2dd9..067717e1 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ The public API of some classes has been modernized to use named parameters for o Version 3.x requires at least Ruby 3.0. -Version 2.x requires at least Ruby 2.4, and is known to work on Ruby 3.1. +Version 2.x requires at least Ruby 2.4, and is known to work on Ruby 3.x. It is not recommended to use any versions of Rubyzip earlier than 2.3 due to security issues. From 8ed666289129ffe2b826ccec4824290520b843f0 Mon Sep 17 00:00:00 2001 From: Geremia Taglialatela Date: Mon, 6 Jan 2025 23:03:49 +0100 Subject: [PATCH 463/469] Fix JRuby CI tests Pin `jar-dependencies` to `0.4.1`. Ref: jruby/jruby#7262 Close #626 --- Gemfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Gemfile b/Gemfile index 7f4f5e95..93106acb 100644 --- a/Gemfile +++ b/Gemfile @@ -3,3 +3,7 @@ source 'https://rubygems.org' gemspec + +# TODO: remove when JRuby 9.4.10.0 will be released and available on CI +# Ref: https://github.com/jruby/jruby/issues/7262 +gem 'jar-dependencies', '0.4.1' if RUBY_PLATFORM.include?('java') From f7c6b79256f99311231747ff9cc6a169a2014628 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 25 Jan 2025 09:55:27 +0000 Subject: [PATCH 464/469] Update some dev dependency gems and relax version matching. For gems such as rake, minitest and rdoc it is appropriate to allow version matches against major versions, allowing us to get the latest minor and patch versions automatically. Also update the minimum minor versions while we are at it. We'll leave rubocop* and simplecov* gems pinned to minor versions. --- rubyzip.gemspec | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rubyzip.gemspec b/rubyzip.gemspec index 7392d538..66ec3657 100644 --- a/rubyzip.gemspec +++ b/rubyzip.gemspec @@ -28,9 +28,9 @@ Gem::Specification.new do |s| s.required_ruby_version = '>= 3.0' - s.add_development_dependency 'minitest', '~> 5.22.0' - s.add_development_dependency 'rake', '~> 13.1.0' - s.add_development_dependency 'rdoc', '~> 6.6.2' + s.add_development_dependency 'minitest', '~> 5.25' + s.add_development_dependency 'rake', '~> 13.2' + s.add_development_dependency 'rdoc', '~> 6.11' s.add_development_dependency 'rubocop', '~> 1.61.0' s.add_development_dependency 'rubocop-performance', '~> 1.20.0' s.add_development_dependency 'rubocop-rake', '~> 0.6.0' From 3f909b2bdc89e406ce26fd864b110f4b1b90197b Mon Sep 17 00:00:00 2001 From: Geremia Taglialatela Date: Sat, 11 Jan 2025 11:21:42 +0100 Subject: [PATCH 465/469] Fix CI against JRuby, JRuby-head, and Windows Additionally: - Use `latest` rubygems, instead of specifying a version and keeping it up to date - Bump `rake` dependency to `~> 13.2.0` to allow tests to pass against Windows --- .github/workflows/tests.yml | 2 +- Gemfile | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e20d5f94..a09e80d8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -25,7 +25,7 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} - rubygems: '3.5.23' + rubygems: latest bundler-cache: true - name: Run the tests diff --git a/Gemfile b/Gemfile index 93106acb..bdfaab79 100644 --- a/Gemfile +++ b/Gemfile @@ -6,4 +6,7 @@ gemspec # TODO: remove when JRuby 9.4.10.0 will be released and available on CI # Ref: https://github.com/jruby/jruby/issues/7262 -gem 'jar-dependencies', '0.4.1' if RUBY_PLATFORM.include?('java') +if RUBY_PLATFORM.include?('java') + gem 'jar-dependencies', '0.4.1' + gem 'ruby-maven', '3.3.13' +end From 43d845c2cbe989068952132e0d7b23c91e01d1d7 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 26 Jan 2025 17:17:11 +0000 Subject: [PATCH 466/469] Update version number, README and Changelog for RC1. Also update the year to 2025 in the licence. --- Changelog.md | 5 +++++ LICENSE.md | 2 +- README.md | 15 +++++++-------- lib/zip/version.rb | 2 +- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/Changelog.md b/Changelog.md index ea6e39c1..06b40c17 100644 --- a/Changelog.md +++ b/Changelog.md @@ -39,6 +39,11 @@ Tooling/internal: +- Update the README with new Ruby version compatability information. +- Fix various issues with JRuby tests. +- Update gem dependency versions. +- Add Ruby 3.4 to the CI. +- Fix mispelled variable names in the crypto classes. - Only use the Zip64 CDIR end locator if needed. - Prevent unnecessary Zip64 data being stored. - Abstract marking various things as 'dirty' into `Dirtyable` for reuse. diff --git a/LICENSE.md b/LICENSE.md index 16e431c1..5673f4bc 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ BSD 2-Clause License -Copyright (c) 2002-2024, The Rubyzip Developers +Copyright (c) 2002-2025, The Rubyzip Developers Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/README.md b/README.md index 067717e1..dcf01eda 100644 --- a/README.md +++ b/README.md @@ -388,22 +388,21 @@ Rubyzip is known to run on a number of platforms and under a number of different ### Version 2.3.x -Rubyzip 2.3 is known to work on MRI 2.4 to 3.1 on Linux and Mac, and JRuby and Truffleruby on Linux. There are known issues with Windows which have been fixed on the development branch. Please [let us know](https://github.com/rubyzip/rubyzip/pulls) if you know Rubyzip 2.3 works on a platform/Ruby combination not listed here, or [raise an issue](https://github.com/rubyzip/rubyzip/issues) if you see a failure where we think it should work. +Rubyzip 2.3 is known to work on MRI 2.4 to 3.4 on Linux and Mac, and JRuby and Truffleruby on Linux. There are known issues with Windows which have been fixed on the development branch. Please [let us know](https://github.com/rubyzip/rubyzip/pulls) if you know Rubyzip 2.3 works on a platform/Ruby combination not listed here, or [raise an issue](https://github.com/rubyzip/rubyzip/issues) if you see a failure where we think it should work. ### Next (version 3.0.0) Please see the table below for what we think the current situation is. Note: an empty cell means "unknown", not "does not work". -| OS/Ruby | 2.5 | 2.6 | 2.7 | 3.0 | 3.1 | 3.2 | 3.3 | Head | JRuby 9.4.6.0 | JRuby Head | Truffleruby 23.1.2 | Truffleruby Head | -|---------|-----|-----|-----|-----|-----|-----|-----|------|---------------|------------|--------------------|------------------| -|Ubuntu 22.04| CI | CI | CI | CI | CI | CI | CI | ci | CI | ci | CI | ci | -|Mac OS 12.7.3| CI | x | x | ci | ci | ci | ci | ci | x | | x | | -|Windows 10| | | x | | | | | | | | | | -|Windows Server 2022| CI | | | | | | CI mswin
CI ucrt | | | | | | +| OS/Ruby | 3.0 | 3.1 | 3.2 | 3.3 | 3.4 | Head | JRuby 9.4.9.0 | JRuby Head | Truffleruby 24.1.1 | Truffleruby Head | +|---------|-----|-----|-----|-----|-----|------|---------------|------------|--------------------|------------------| +|Ubuntu 22.04| CI | CI | CI | CI | CI | ci | CI | ci | CI | ci | +|Mac OS 14.7.2| CI | CI | CI | CI | CI | ci | x | | x | | +|Windows Server 2022| CI | | | | CI mswin
CI ucrt | | | | | | Key: `CI` - tested in CI, should work; `ci` - tested in CI, might fail; `x` - known working; `o` - known failing. -Ruby 3.0+ are also tested separately with YJIT turned on. +Rubies 3.1+ are also tested separately with YJIT turned on (Ubuntu and Mac OS). See [the Actions tab](https://github.com/rubyzip/rubyzip/actions) in GitHub for full details. diff --git a/lib/zip/version.rb b/lib/zip/version.rb index 8878fc45..12555e21 100644 --- a/lib/zip/version.rb +++ b/lib/zip/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Zip - VERSION = '3.0.0.alpha' # :nodoc: + VERSION = '3.0.0.rc1' # :nodoc: end From 98881e23d1e5f43b104533f752a9e177509714c2 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 1 Feb 2025 16:31:20 +0000 Subject: [PATCH 467/469] Add a test to ensure correct version number format. Hopefully this will avoid a repeat of the '2.4' debacle... --- test/version_test.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 test/version_test.rb diff --git a/test/version_test.rb b/test/version_test.rb new file mode 100644 index 00000000..3df7662b --- /dev/null +++ b/test/version_test.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'test_helper' +require 'zip/version' + +class VersionTest < MiniTest::Test + def test_version + # Ensure all our versions numbers have at least MAJOR.MINOR.PATCH + # elements separated by dots, to comply with Semantic Versioning. + assert_match(/^\d+\.\d+\.\d+/, Zip::VERSION) + end +end From deca4d5aeb0f662bbe6cb8c11017dd524f008fb8 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 8 Feb 2025 16:51:30 +0000 Subject: [PATCH 468/469] Fix de facto regression for input streams. A close reading of the ZIP spec insists that if bit 3 of the GP flags is set then the archive cannot be read via `Zip::InputStream`. But in most cases the correct information is present to be able to do so, both safely and reliably, and v2.4 does allow this. This commit ensures that behaviour is present in v3.0. --- lib/zip/entry.rb | 35 +++++++++++++++++++++++------------ test/file_split_test.rb | 9 +++++++++ 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 9e1a904c..394f9190 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -113,7 +113,7 @@ def encrypted? end def incomplete? # :nodoc: - gp_flags & 8 == 8 + (gp_flags & 8 == 8) && (crc == 0 || size == 0 || compressed_size == 0) end # The uncompressed size of the entry. @@ -343,24 +343,25 @@ def unpack_local_entry(buf) # :nodoc: def read_local_entry(io) # :nodoc: @dirty = false # No changes at this point. - @local_header_offset = io.tell + current_offset = io.tell - static_sized_fields_buf = io.read(::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH) || '' + read_local_header_fields(io) - unless static_sized_fields_buf.bytesize == ::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH - raise Error, 'Premature end of file. Not enough data for zip entry local header' - end + if @header_signature == SPLIT_FILE_SIGNATURE + raise SplitArchiveError if current_offset.zero? - unpack_local_entry(static_sized_fields_buf) + # Rewind, skipping the data descriptor, then try to read the local header again. + current_offset += 16 + io.seek(current_offset) + read_local_header_fields(io) + end unless @header_signature == LOCAL_ENTRY_SIGNATURE - if @header_signature == SPLIT_FILE_SIGNATURE - raise SplitArchiveError - end - - raise Error, "Zip local header magic not found at location '#{local_header_offset}'" + raise Error, "Zip local header magic not found at location '#{current_offset}'" end + @local_header_offset = current_offset + set_time(@last_mod_date, @last_mod_time) @name = io.read(@name_length) @@ -721,6 +722,16 @@ def clean_up # :nodoc: private + def read_local_header_fields(io) # :nodoc: + static_sized_fields_buf = io.read(::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH) || '' + + unless static_sized_fields_buf.bytesize == ::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH + raise Error, 'Premature end of file. Not enough data for zip entry local header' + end + + unpack_local_entry(static_sized_fields_buf) + end + def set_time(binary_dos_date, binary_dos_time) @time = ::Zip::DOSTime.parse_binary_dos_format(binary_dos_date, binary_dos_time) rescue ArgumentError diff --git a/test/file_split_test.rb b/test/file_split_test.rb index 859bf935..0c5a5eeb 100644 --- a/test/file_split_test.rb +++ b/test/file_split_test.rb @@ -5,6 +5,7 @@ class ZipFileSplitTest < MiniTest::Test TEST_ZIP = TestZipFile::TEST_ZIP2.clone TEST_ZIP.zip_name = 'large_zip_file.zip' + TEST_ZIP_FIRST_SEGMENT = 'large_zip_file.zip.001' EXTRACTED_FILENAME = 'test/data/generated/extEntrySplit' UNSPLITTED_FILENAME = 'test/data/generated/unsplitted.zip' ENTRY_TO_EXTRACT = TEST_ZIP.entry_names.first @@ -59,4 +60,12 @@ def test_split entry.get_input_stream(&:read)) end end + + def test_raise_error_on_open_split_zip + ::Zip::File.split(TEST_ZIP.zip_name, segment_size: 65_536, delete_original: false) + + assert_raises(::Zip::SplitArchiveError) do + ::Zip::InputStream.open(TEST_ZIP_FIRST_SEGMENT, &:get_next_entry) + end + end end From 1f3f84c88914b2b3c77c18b73f2ecb42225a54af Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sat, 8 Feb 2025 17:18:43 +0000 Subject: [PATCH 469/469] Update version number and Changelog for RC2. --- Changelog.md | 2 ++ lib/zip/version.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 06b40c17..7e0b9052 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,6 @@ # 3.0.0 (Next) +- Fix de facto regression for input streams. - Fix `File#write_buffer` to always return the given `io`. - Add `Entry#absolute_time?` and `DOSTime#absolute_time?` methods. - Use explicit named parameters for `File` methods. @@ -39,6 +40,7 @@ Tooling/internal: +- Add a test to ensure correct version number format. - Update the README with new Ruby version compatability information. - Fix various issues with JRuby tests. - Update gem dependency versions. diff --git a/lib/zip/version.rb b/lib/zip/version.rb index 12555e21..7957e0de 100644 --- a/lib/zip/version.rb +++ b/lib/zip/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Zip - VERSION = '3.0.0.rc1' # :nodoc: + VERSION = '3.0.0.rc2' # :nodoc: end

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