diff --git a/.gitignore b/.gitignore index 0e484c38..f870b5cb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,10 @@ .idea *.gem -test/5entry_copy.zip -test/cdirtest.bin -test/centralEntryHeader.bin -test/data/generated/ -test/deflatertest.bin -test/dummy.txt -test/localEntryHeader.bin -test/okToDeleteMoved.txt -test/output.zip -test/test_putOnClosedStream.zip -test/zipWithDirs_copy.zip .bundle Gemfile.lock +samples/*.zip -+samples/*.zip.* \ No newline at end of file ++samples/*.zip.* +coverage +pkg/ +.ruby-gemset +.ruby-version diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 00000000..a408fa0d --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,6 @@ +inherit_from: + - .rubocop_rubyzip.yml +AllCops: + TargetRubyVersion: 1.9 +Style/MutableConstant: + Enabled: false # Because some existent code relies on mutable constant diff --git a/.rubocop_rubyzip.yml b/.rubocop_rubyzip.yml new file mode 100644 index 00000000..3030f8a0 --- /dev/null +++ b/.rubocop_rubyzip.yml @@ -0,0 +1,137 @@ +# 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/.simplecov b/.simplecov new file mode 100644 index 00000000..770d7dc6 --- /dev/null +++ b/.simplecov @@ -0,0 +1,9 @@ +require 'coveralls' + +SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([ + SimpleCov::Formatter::HTMLFormatter, + Coveralls::SimpleCov::Formatter +]) +SimpleCov.start do + add_filter '/test' +end diff --git a/.travis.yml b/.travis.yml index 9a3297a1..5b299c64 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,30 @@ +language: ruby +dist: trusty +cache: bundler rvm: - - 1.8.7 - - 1.9.2 - - 1.9.3 - - 2.0.0 -before_script: "sudo apt-get -y install zip unzip" -branches: - only: - - master + - 2.4 + - 2.5 + - 2.6 + - ruby-head +matrix: + include: + - rvm: jruby-9.2 + jdk: openjdk8 + - rvm: jruby-head + jdk: openjdk8 + - rvm: rbx-4 + allow_failures: + - rvm: ruby-head + - rvm: rbx-4 + - rvm: jruby-head +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/Changelog.md b/Changelog.md new file mode 100644 index 00000000..f8bff465 --- /dev/null +++ b/Changelog.md @@ -0,0 +1,294 @@ +# 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) + +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 + +- 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 + +- Add more gem metadata links [#402](https://github.com/rubyzip/rubyzip/pull/402) + +# 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) +- 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 + +- 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) + +# 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) + +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 + +# 1.2.0 + +- 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.7 + +- 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.6 + +- Revert "Return created zip file from Zip::File.open when supplied a block" + +# 1.1.5 + +- 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.4 + +- 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.3 + +- Fix compatibility of ::OutputStream::write_buffer (@orien) +- Clean up tempfiles from output stream (@iangreenleaf) + +# 1.1.2 + +- Fix compatibility of ::Zip::File.write_buffer + +# 1.1.1 + +- 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.1.0 + +- StringIO Support +- Zip64 Support +- Better jRuby Support +- Order of files in the archive can be sorted +- Other small fixes + +# 1.0.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. + +# 0.9.9 + +- 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.8 + +- Fixed: "Unitialized constant NullInputStream" error + +# 0.9.5 + +- Removed support for loading ruby in zip files (ziprequire.rb). + +# 0.9.4 + +- Changed ZipOutputStream.put_next_entry signature (API CHANGE!). Now allows comment, extra field and compression method to be specified. + +# 0.9.3 + +- 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.2 + +- Fixed: Renaming an entry failed if the entry's new name was a different length than its old name. (Diego Barros) + +# 0.9.1 + +- 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.12 + +- Fixed problem with writing binary content to a ZipFile in MS Windows. + +# 0.5.11 + +- 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.10 + +- Fixed method name resolution problem with FileUtils::copy_stream and IOExtras::copy_stream. + +# 0.5.9 + +- Fixed serious memory consumption issue + +# 0.5.8 + +- Fixed install script. + +# 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.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. + +# 0.5.5 + +- Fix for a problem with writing zip files that concerns only ruby 1.8.1. + +# 0.5.4 + +- Significantly reduced memory footprint when modifying zip files. + +# 0.5.3 + +- Added optimization to avoid decompressing and recompressing individual entries when modifying a zip archive. + +# 0.5.2 + +- Fixed ZipFile corruption bug in ZipFile class. Added basic unix extra-field support. + +# 0.5.1 + +- Fixed ZipFile.get_output_stream bug. + +# 0.5.0 + +- 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 + +# 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: + +- 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.4.2 + +- Performance optimizations. Test suite runs in half the time. + +# 0.4.1 + +- Windows compatibility fixes. + +# 0.4.0 + +- 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. + +# 0.3.1 + +- Rudimentary support for writing zip archives. + +# 0.2.2 + +- 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.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 + +- First working version of ZipInputStream. diff --git a/Gemfile b/Gemfile index 66704f6e..fa75df15 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,3 @@ -source :rubygems +source 'https://rubygems.org' gemspec - -gem 'rake' diff --git a/Guardfile b/Guardfile new file mode 100644 index 00000000..1508e4c9 --- /dev/null +++ b/Guardfile @@ -0,0 +1,6 @@ +guard :minitest do + # with Minitest::Unit + 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/NEWS b/NEWS deleted file mode 100644 index 959d46e8..00000000 --- a/NEWS +++ /dev/null @@ -1,176 +0,0 @@ -= Version 0.9.9 - -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. - -= Version 0.9.8 - -Fixed: "Unitialized constant NullInputStream" error - -= Version 0.9.5 - -Removed support for loading ruby in zip files (ziprequire.rb). - -= Version 0.9.4 - -Changed ZipOutputStream.put_next_entry signature (API CHANGE!). Now -allows comment, extra field and compression method to be specified. - -= Version 0.9.3 - -Fixed: Added ZipEntry::name_encoding which retrieves the character -encoding of the name and comment of the entry. Also added convenience -methods ZipEntry::name_in(enc) and ZipEntry::comment_in(enc) for -getting zip entry names and comments in a specified character -encoding. - -= Version 0.9.2 - -Fixed: Renaming an entry failed if the entry's new name was a -different length than its old name. (Diego Barros) - -= Version 0.9.1 - -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. - -= Version 0.5.12 - -Fixed problem with writing binary content to a ZipFile in MS Windows. - -= Version 0.5.11 - -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. - -= Version 0.5.10 - -Fixed method name resolution problem with FileUtils::copy_stream and -IOExtras::copy_stream. - -= Version 0.5.9 - -Fixed serious memory consumption issue - -= Version 0.5.8 - -Fixed install script. - -= Version 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. - -= Version 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. - -= Version 0.5.5 - -Fix for a problem with writing zip files that concerns only ruby 1.8.1. - -= Version 0.5.4 - -Significantly reduced memory footprint when modifying zip files. - -= Version 0.5.3 - -Added optimization to avoid decompressing and recompressing individual -entries when modifying a zip archive. - -= Version 0.5.2 - -Fixed ZipFile corruption bug in ZipFile class. Added basic unix -extra-field support. - -= Version 0.5.1 - -Fixed ZipFile.get_output_stream bug. - -= Version 0.5.0 - -List of changes: -* 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. - -Bug fixes: -* AbstractInputStream.each_line with non-default separator - - -= Version 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 gtkRubyzip, another sample application. Implemented -ZipInputStream.lineno and ZipInputStream.rewind - -Bug fixes: - -* 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. - -= Version 0.4.2 - -Performance optimizations. Test suite runs in half the time. - -= Version 0.4.1 - -Windows compatibility fixes. - -= Version 0.4.0 - -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. - - -= Version 0.3.1 - -Rudimentary support for writing zip archives. - - -= Version 0.2.2 - -Fixed and extended unit test suite. Updated to work with ruby/zlib -0.5. It doesn't work with earlier versions of ruby/zlib. - - -= Version 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. - - -= Version 0.1.0 - -First working version of ZipInputStream. diff --git a/README.md b/README.md index e6301be3..059f22d1 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,30 @@ -# rubyzip [![Build Status](https://secure.travis-ci.org/aussiegeek/rubyzip.png)](http://travis-ci.org/aussiegeek/rubyzip)[![Code Climate](https://codeclimate.com/github/aussiegeek/rubyzip.png)](https://codeclimate.com/github/aussiegeek/rubyzip) +# rubyzip -rubyzip is a ruby library for reading and writing zip files. +[![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) +[![Coverage Status](https://img.shields.io/coveralls/rubyzip/rubyzip.svg)](https://coveralls.io/r/rubyzip/rubyzip?branch=master) + +Rubyzip is a ruby library for reading and writing zip files. + +## Important note + +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 + +- Ruby 2.4 or greater (for rubyzip 2.0; use 1.x for older rubies) ## Installation -rubyzip is available on RubyGems, so: + +Rubyzip is available on RubyGems: ``` gem install rubyzip @@ -21,39 +42,160 @@ gem 'rubyzip' ```ruby require 'rubygems' -require 'zip/zip' +require 'zip' folder = "Users/me/Desktop/stuff_to_zip" input_filenames = ['image.jpg', 'description.txt', 'stats.csv'] zipfile_name = "/Users/me/Desktop/archive.zip" -Zip::ZipFile.open(zipfile_name, Zip::ZipFile::CREATE) do |zipfile| +Zip::File.open(zipfile_name, Zip::File::CREATE) do |zipfile| input_filenames.each do |filename| # Two arguments: # - The name of the file as it will appear in the archive # - The original file, including the path to find it - zipfile.add(filename, folder + '/' + filename) + zipfile.add(filename, File.join(folder, filename)) end + zipfile.get_output_stream("myFile") { |f| f.write "myFile contains just this" } end ``` ### Zipping a directory recursively +Copy from [here](https://github.com/rubyzip/rubyzip/blob/9d891f7353e66052283562d3e252fe380bb4b199/samples/example_recursive.rb) + ```ruby -require 'rubygems' -require 'zip/zip' +require 'zip' + +# This is a simple example which uses rubyzip to +# recursively generate a zip file from the contents of +# a specified directory. The directory itself is not +# included in the archive, rather just its contents. +# +# Usage: +# directory_to_zip = "/tmp/input" +# output_file = "/tmp/out.zip" +# zf = ZipFileGenerator.new(directory_to_zip, output_file) +# zf.write() +class ZipFileGenerator + # Initialize with the directory to zip and the location of the output archive. + def initialize(input_dir, output_file) + @input_dir = input_dir + @output_file = output_file + end -directory = '/Users/me/Desktop/directory_to_zip/' -zipfile_name = '/Users/me/Desktop/recursive_directory.zip' + # Zip the input directory. + def write + entries = Dir.entries(@input_dir) - %w[. ..] -Zip::ZipFile.open(zipfile_name, Zip::ZipFile::CREATE) do |zipfile| - Dir[File.join(directory, '**', '**')].each do |file| - zipfile.add(file.sub(directory, ''), file) - end + ::Zip::File.open(@output_file, ::Zip::File::CREATE) do |zipfile| + write_entries entries, '', zipfile + end + end + + private + + # A helper method to make the recursion work. + def write_entries(entries, path, zipfile) + entries.each do |e| + zipfile_path = path == '' ? e : File.join(path, e) + disk_file_path = File.join(@input_dir, zipfile_path) + + if File.directory? disk_file_path + recursively_deflate_directory(disk_file_path, zipfile, zipfile_path) + else + put_into_archive(disk_file_path, zipfile, zipfile_path) + end + end + end + + def recursively_deflate_directory(disk_file_path, zipfile, zipfile_path) + zipfile.mkdir zipfile_path + 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.add(zipfile_path, disk_file_path) + end end ``` +### Save zip archive entries in sorted by name state + +To save zip archives in sorted order like below, you need to set `::Zip.sort_entries` to `true` + +``` +Vegetable/ +Vegetable/bean +Vegetable/carrot +Vegetable/celery +fruit/ +fruit/apple +fruit/kiwi +fruit/mango +fruit/orange +``` + +After this, entries in the zip archive will be saved in ordered state. + +### Default permissions of zip archives + +On Posix file systems the default file permissions applied to a new archive +are (0666 - umask), which mimics the behavior of standard tools such as `touch`. + +On Windows the default file permissions are set to 0644 as suggested by the +[Ruby File documentation](http://ruby-doc.org/core-2.2.2/File.html). + +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| + puts "Extracting #{entry.name}" + 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 + end + + # 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 +``` + +#### Notice about ::Zip::InputStream + +`::Zip::InputStream` usable for fast reading zip file content because it not read Central directory. + +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) + +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| + out.put_next_entry("my_file.txt") + out.write my_data +end.string +``` + +This is an experimental feature and the interface for encryption may change in future versions. + ## Known issues ### Modify docx file with rubyzip @@ -61,69 +203,127 @@ end Use `write_buffer` instead `open`. Thanks to @jondruse ```ruby -buffer = Zip::ZipOutputStream.write_buffer do |out| +buffer = Zip::OutputStream.write_buffer do |out| @zip_file.entries.each do |e| 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 - + out.put_next_entry(DOCUMENT_FILE_PATH) out.write xml_doc.to_xml(:indent => 0).gsub("\n","") - + out.put_next_entry(RELS_FILE_PATH) out.write rels.to_xml(:indent => 0).gsub("\n","") end -File.open(new_path, "w") {|f| f.write(buffer.string) } +File.open(new_path, "wb") {|f| f.write(buffer.string) } ``` -## Further Documentation +## Configuration -There is more than one way to access or create a zip archive with -rubyzip. The basic API is modeled after the classes in -java.util.zip from the Java SDK. This means there are classes such -as Zip::ZipInputStream, Zip::ZipOutputStream and -Zip::ZipFile. Zip::ZipInputStream provides a basic interface for -iterating through the entries in a zip archive and reading from the -entries in the same way as from a regular File or IO -object. ZipOutputStream is the corresponding basic output -facility. Zip::ZipFile provides a mean for accessing the archives -central directory and provides means for accessing any entry without -having to iterate through the archive. Unlike Java's -java.util.zip.ZipFile rubyzip's Zip::ZipFile is mutable, which means -it can be used to change zip files as well. +### Existing Files -Another way to access a zip archive with rubyzip is to use rubyzip's -Zip::ZipFileSystem API. Using this API files can be read from and -written to the archive in much the same manner as ruby's builtin -classes allows files to be read from and written to the file system. +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: -For details about the specific behaviour of classes and methods refer -to the test suite. Finally you can generate the rdoc documentation or -visit http://rubyzip.sourceforge.net. +```ruby +Zip.on_exists_proc = true +``` +If you're using rubyzip with rails, consider placing this snippet of code in an initializer file such as `config/initializers/rubyzip.rb` -## Configuration +Additionally, if you want to configure rubyzip to overwrite existing files while creating a .zip file, you can do so with the following: + +```ruby +Zip.continue_on_exists_proc = true +``` + +### Non-ASCII Names -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: +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 ``` -Zip.options[:on_exists_proc] = 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' ``` -If you're using rubyzip with rails, consider placing this snippet of code in an initializer file such as `config/initializers/rubyzip.rb` +Allowed encoding names are the same as accepted by `String#force_encoding` -Additionally, if you want to configure rubyzip to overwrite existing files while creating a .zip file, you can do so with the following: +### 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 ``` -Zip.options[:continue_on_exists_proc] = true + +### Size Validation + +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 +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 +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` + +### Zip64 Support + +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. + +### Block Form + +You can set multiple settings at the same time by using a block: + +```ruby + Zip.setup do |c| + c.on_exists_proc = true + c.continue_on_exists_proc = true + c.unicode_names = true + c.default_compression = Zlib::BEST_COMPRESSION + end ``` ## Developing -To run tests you need run next commands: +To run the test you need to do this: ``` bundle install @@ -132,9 +332,9 @@ rake ## Website and Project Home -http://github.com/aussiegeek/rubyzip +http://github.com/rubyzip/rubyzip -http://rdoc.info/github/aussiegeek/rubyzip/master/frames +http://rdoc.info/github/rubyzip/rubyzip/master/frames ## Authors @@ -150,5 +350,5 @@ extra-field support contributed by Tatsuki Sugiura (sugi at nemui.org) ## License -rubyzip is distributed under the same license as ruby. See +Rubyzip is distributed under the same license as ruby. See http://www.ruby-lang.org/en/LICENSE.txt diff --git a/Rakefile b/Rakefile index ed9097d5..44a9b287 100644 --- a/Rakefile +++ b/Rakefile @@ -1,13 +1,18 @@ -require 'rubygems' +require 'bundler/gem_tasks' require 'rake/testtask' -task :default => [:test] +task default: :test Rake::TestTask.new(: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/alltests.rb') + test.libs << 'lib' + test.libs << 'test' + test.pattern = 'test/**/*_test.rb' test.verbose = true - Dir.chdir File.join(File.dirname(__FILE__), 'test') end +# 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 diff --git a/TODO b/TODO index 0e89e394..16b9a2e7 100644 --- a/TODO +++ b/TODO @@ -4,7 +4,6 @@ * 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 -* Suggestion: ZipInputStream/ZipOutputStream should accept an IO object in addition to a filename. * (is buffering used anywhere with write?) * Inflater.sysread should pass the buffer to produce_input. * Implement ZipFsDir.glob diff --git a/lib/zip.rb b/lib/zip.rb new file mode 100644 index 00000000..fa382376 --- /dev/null +++ b/lib/zip.rb @@ -0,0 +1,70 @@ +require 'delegate' +require 'singleton' +require 'tempfile' +require 'fileutils' +require 'stringio' +require 'zlib' +require 'zip/dos_time' +require 'zip/ioextras' +require 'rbconfig' +require 'zip/entry' +require 'zip/extra_field' +require 'zip/entry_set' +require 'zip/central_directory' +require 'zip/file' +require 'zip/input_stream' +require 'zip/output_stream' +require 'zip/decompressor' +require 'zip/compressor' +require 'zip/null_decompressor' +require 'zip/null_compressor' +require 'zip/null_input_stream' +require 'zip/pass_thru_compressor' +require 'zip/pass_thru_decompressor' +require 'zip/crypto/encryption' +require 'zip/crypto/null_encryption' +require 'zip/crypto/traditional_encryption' +require 'zip/inflater' +require 'zip/deflater' +require 'zip/streamable_stream' +require 'zip/streamable_directory' +require 'zip/constants' +require 'zip/errors' + +module Zip + extend self + attr_accessor :unicode_names, + :on_exists_proc, + :continue_on_exists_proc, + :sort_entries, + :default_compression, + :write_zip64_support, + :warn_invalid_date, + :case_insensitive_match, + :force_entry_names_encoding, + :validate_entry_sizes + + def reset! + @_ran_once = false + @unicode_names = false + @on_exists_proc = false + @continue_on_exists_proc = false + @sort_entries = false + @default_compression = ::Zlib::DEFAULT_COMPRESSION + @write_zip64_support = false + @warn_invalid_date = true + @case_insensitive_match = false + @validate_entry_sizes = true + end + + def setup + yield self unless @_ran_once + @_ran_once = true + end + + reset! +end + +# Copyright (C) 2002, 2003 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/lib/zip/central_directory.rb b/lib/zip/central_directory.rb new file mode 100644 index 00000000..0b6874ef --- /dev/null +++ b/lib/zip/central_directory.rb @@ -0,0 +1,208 @@ +module Zip + class CentralDirectory + include Enumerable + + 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 + + attr_reader :comment + + # Returns an Enumerable containing the entries. + def entries + @entry_set.entries + end + + def initialize(entries = EntrySet.new, comment = '') #:nodoc: + super() + @entry_set = entries.kind_of?(EntrySet) ? entries : EntrySet.new(entries) + @comment = comment + end + + def write_to_stream(io) #:nodoc: + cdir_offset = io.tell + @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? { |entry| entry.extra['Zip64'] } + if need_zip64_eocd + write_64_e_o_c_d(io, cdir_offset, cdir_size) + write_64_eocd_locator(io, eocd_offset) + end + end + write_e_o_c_d(io, cdir_offset, cdir_size) + end + + def write_e_o_c_d(io, offset, cdir_size) #:nodoc: + tmp = [ + END_OF_CDS, + 0, # @numberOfThisDisk + 0, # @numberOfDiskWithStartOfCDir + @entry_set ? [@entry_set.size, 0xFFFF].min : 0, + @entry_set ? [@entry_set.size, 0xFFFF].min : 0, + [cdir_size, 0xFFFFFFFF].min, + [offset, 0xFFFFFFFF].min, + @comment ? @comment.bytesize : 0 + ] + io << tmp.pack('VvvvvVVv') + 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_CDS, + 44, # size of zip64 end of central directory record (excludes signature and field itself) + VERSION_MADE_BY, + VERSION_NEEDED_TO_EXTRACT_ZIP64, + 0, # @numberOfThisDisk + 0, # @numberOfDiskWithStartOfCDir + @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 + ] + io << tmp.pack('VQ '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 + }.freeze end diff --git a/lib/zip/crypto/encryption.rb b/lib/zip/crypto/encryption.rb new file mode 100644 index 00000000..4351be1c --- /dev/null +++ b/lib/zip/crypto/encryption.rb @@ -0,0 +1,11 @@ +module Zip + class Encrypter #:nodoc:all + end + + class Decrypter + end +end + +# Copyright (C) 2002, 2003 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/lib/zip/crypto/null_encryption.rb b/lib/zip/crypto/null_encryption.rb new file mode 100644 index 00000000..a93f707c --- /dev/null +++ b/lib/zip/crypto/null_encryption.rb @@ -0,0 +1,43 @@ +module Zip + module NullEncryption + def header_bytesize + 0 + end + + def gp_flags + 0 + end + end + + class NullEncrypter < Encrypter + include NullEncryption + + def header(_mtime) + '' + end + + def encrypt(data) + data + end + + def data_descriptor(_crc32, _compressed_size, _uncomprssed_size) + '' + end + + def reset!; end + end + + class NullDecrypter < Decrypter + include NullEncryption + + def decrypt(data) + data + end + + def reset!(_header); end + end +end + +# Copyright (C) 2002, 2003 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/lib/zip/crypto/traditional_encryption.rb b/lib/zip/crypto/traditional_encryption.rb new file mode 100644 index 00000000..91e6ce16 --- /dev/null +++ b/lib/zip/crypto/traditional_encryption.rb @@ -0,0 +1,99 @@ +module Zip + module TraditionalEncryption + def initialize(password) + @password = password + reset_keys! + end + + def header_bytesize + 12 + end + + def gp_flags + 0x0001 | 0x0008 + end + + protected + + def reset_keys! + @key0 = 0x12345678 + @key1 = 0x23456789 + @key2 = 0x34567890 + @password.each_byte do |byte| + update_keys(byte.chr) + end + end + + def update_keys(n) + @key0 = ~Zlib.crc32(n, ~@key0) + @key1 = ((@key1 + (@key0 & 0xff)) * 134_775_813 + 1) & 0xffffffff + @key2 = ~Zlib.crc32((@key1 >> 24).chr, ~@key2) + end + + def decrypt_byte + temp = (@key2 & 0xffff) | 2 + ((temp * (temp ^ 1)) >> 8) & 0xff + end + end + + class TraditionalEncrypter < Encrypter + include TraditionalEncryption + + def header(mtime) + [].tap do |header| + (header_bytesize - 2).times do + header << Random.rand(0..255) + end + header << (mtime.to_binary_dos_time & 0xff) + header << (mtime.to_binary_dos_time >> 8) + end.map { |x| encode x }.pack('C*') + end + + 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') + end + + def reset! + reset_keys! + end + + private + + def encode(n) + t = decrypt_byte + update_keys(n.chr) + t ^ n + end + end + + class TraditionalDecrypter < Decrypter + include TraditionalEncryption + + def decrypt(data) + data.unpack('C*').map { |x| decode x }.pack('C*') + end + + def reset!(header) + reset_keys! + header.each_byte do |x| + decode x + end + end + + private + + def decode(n) + n ^= decrypt_byte + update_keys(n.chr) + n + end + end +end + +# Copyright (C) 2002, 2003 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/lib/zip/decompressor.rb b/lib/zip/decompressor.rb old mode 100755 new mode 100644 index 8ccdee54..047ed5e7 --- a/lib/zip/decompressor.rb +++ b/lib/zip/decompressor.rb @@ -1,9 +1,9 @@ module Zip - class Decompressor #:nodoc:all - CHUNK_SIZE=32768 - def initialize(inputStream) + class Decompressor #:nodoc:all + CHUNK_SIZE = 32_768 + def initialize(input_stream) super() - @inputStream=inputStream + @input_stream = input_stream end end end diff --git a/lib/zip/deflater.rb b/lib/zip/deflater.rb old mode 100755 new mode 100644 index 1ec24057..8509cf47 --- a/lib/zip/deflater.rb +++ b/lib/zip/deflater.rb @@ -1,24 +1,28 @@ module Zip class Deflater < Compressor #:nodoc:all - def initialize(outputStream, level = Zlib::DEFAULT_COMPRESSION) + def initialize(output_stream, level = Zip.default_compression, encrypter = NullEncrypter.new) super() - @outputStream = outputStream - @zlibDeflater = Zlib::Deflate.new(level, -Zlib::MAX_WBITS) - @size = 0 - @crc = Zlib::crc32 + @output_stream = output_stream + @zlib_deflater = ::Zlib::Deflate.new(level, -::Zlib::MAX_WBITS) + @size = 0 + @crc = ::Zlib.crc32 + @encrypter = encrypter end - - def << (data) - val = data.to_s - @crc = Zlib::crc32(val, @crc) + + def <<(data) + val = data.to_s + @crc = Zlib.crc32(val, @crc) @size += val.bytesize - @outputStream << @zlibDeflater.deflate(data) + buffer = @zlib_deflater.deflate(data) + if buffer.empty? + @output_stream + else + @output_stream << @encrypter.encrypt(buffer) + end end def finish - until @zlibDeflater.finished? - @outputStream << @zlibDeflater.finish - end + @output_stream << @encrypter.encrypt(@zlib_deflater.finish) until @zlib_deflater.finished? end attr_reader :size, :crc diff --git a/lib/zip/dos_time.rb b/lib/zip/dos_time.rb old mode 100755 new mode 100644 index 74dc4cce..bf0cb7e0 --- a/lib/zip/dos_time.rb +++ b/lib/zip/dos_time.rb @@ -1,7 +1,6 @@ module Zip class DOSTime < Time #:nodoc:all - - #MS-DOS File Date and Time format as used in Interrupt 21H Function 57H: + # MS-DOS File Date and Time format as used in Interrupt 21H Function 57H: # Register CX, the Time: # Bits 0-4 2 second increments (0-29) @@ -14,31 +13,31 @@ class DOSTime < Time #:nodoc:all # bits 9-15 year (four digit year minus 1980) def to_binary_dos_time - (sec/2) + - (min << 5) + - (hour << 11) + (sec / 2) + + (min << 5) + + (hour << 11) end def to_binary_dos_date - (day) + - (month << 5) + - ((year - 1980) << 9) + day + + (month << 5) + + ((year - 1980) << 9) end # Dos time is only stored with two seconds accuracy def dos_equals(other) - to_i/2 == other.to_i/2 + to_i / 2 == other.to_i / 2 end def self.parse_binary_dos_format(binaryDosDate, binaryDosTime) - second = 2 * ( 0b11111 & binaryDosTime) - minute = ( 0b11111100000 & binaryDosTime) >> 5 + second = 2 * (0b11111 & binaryDosTime) + minute = (0b11111100000 & binaryDosTime) >> 5 hour = (0b1111100000000000 & binaryDosTime) >> 11 - day = ( 0b11111 & binaryDosDate) - month = ( 0b111100000 & binaryDosDate) >> 5 + day = (0b11111 & binaryDosDate) + month = (0b111100000 & binaryDosDate) >> 5 year = ((0b1111111000000000 & binaryDosDate) >> 9) + 1980 begin - return self.local(year, month, day, hour, minute, second) + local(year, month, day, hour, minute, second) end end end diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb new file mode 100644 index 00000000..677e49ef --- /dev/null +++ b/lib/zip/entry.rb @@ -0,0 +1,700 @@ +require 'pathname' +module Zip + class Entry + STORED = 0 + DEFLATED = 8 + # Language encoding flag (EFS) bit + EFS = 0b100000000000 + + attr_accessor :comment, :compressed_size, :crc, :extra, :compression_method, + :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 :ftype, :filepath # :nodoc: + + def set_default_vars_values + @local_header_offset = 0 + @local_header_size = nil # not known until local entry is created or read + @internal_file_attributes = 1 + @external_file_attributes = 0 + @header_signature = ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE + + @version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT + @version = VERSION_MADE_BY + + @ftype = nil # unspecified or unknown + @filepath = nil + @gp_flags = 0 + if ::Zip.unicode_names + @gp_flags |= EFS + @version = 63 + end + @follow_symlinks = false + + @restore_times = true + @restore_permissions = false + @restore_ownership = false + # BUG: need an extra field to support uid/gid's + @unix_uid = nil + @unix_gid = nil + @unix_perms = nil + # @posix_acl = nil + # @ntfs_acl = nil + @dirty = false + end + + def check_name(name) + return unless name.start_with?('/') + raise ::Zip::EntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /" + end + + def initialize(*args) + name = args[1] || '' + check_name(name) + + set_default_vars_values + @fstype = ::Zip::RUNNING_ON_WINDOWS ? ::Zip::FSTYPE_FAT : ::Zip::FSTYPE_UNIX + + @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 + @size = args[7] || 0 + @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) + end + + def time + if @extra['UniversalTime'] + @extra['UniversalTime'].mtime + elsif @extra['NTFS'] + @extra['NTFS'].mtime + else + # Standard time field in central directory has local time + # under archive creator. Then, we can't get timezone. + @time + end + end + + alias mtime time + + def time=(value) + unless @extra.member?('UniversalTime') || @extra.member?('NTFS') + @extra.create('UniversalTime') + end + (@extra['UniversalTime'] || @extra['NTFS']).mtime = value + @time = value + end + + def file_type_is?(type) + raise InternalError, "current filetype is unknown: #{inspect}" unless @ftype + @ftype == type + end + + # Dynamic checkers + %w[directory file symlink].each do |k| + define_method "#{k}?" do + file_type_is?(k.to_sym) + end + end + + def name_is_directory? #:nodoc:all + @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? + 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 + end + + def local_entry_offset #:nodoc:all + local_header_offset + @local_header_size + end + + def name_size + @name ? @name.bytesize : 0 + end + + def extra_size + @extra ? @extra.local_size : 0 + end + + def comment_size + @comment ? @comment.bytesize : 0 + end + + def calculate_local_header_size #:nodoc:all + 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! + 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 + + 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 + local_entry_offset + compressed_size + data_descriptor_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? + puts "WARNING: skipped #{@name} as unsafe" + return self + end + + 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 + + self + end + + def to_s + @name + end + + class << self + def read_zip_short(io) # :nodoc: + io.read(2).unpack('v')[0] + end + + def read_zip_long(io) # :nodoc: + io.read(4).unpack('V')[0] + end + + def read_zip_64_long(io) # :nodoc: + io.read(8).unpack('Q<')[0] + end + + def read_c_dir_entry(io) #:nodoc:all + path = if io.respond_to?(:path) + io.path + else + io + end + entry = new(path) + entry.read_c_dir_entry(io) + entry + rescue Error + nil + end + + def read_local_entry(io) + entry = new(io) + entry.read_local_entry(io) + entry + rescue Error + nil + end + end + + public + + def unpack_local_entry(buf) + @header_signature, + @version, + @fstype, + @gp_flags, + @compression_method, + @last_mod_time, + @last_mod_date, + @crc, + @compressed_size, + @size, + @name_length, + @extra_length = buf.unpack('VCCvvvvVVVvv') + end + + def read_local_entry(io) #:nodoc:all + @local_header_offset = io.tell + + 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) + + 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) + extra = io.read(@extra_length) + + @name.tr!('\\', '/') + if ::Zip.force_entry_names_encoding + @name.force_encoding(::Zip.force_entry_names_encoding) + end + + if extra && extra.bytesize != @extra_length + raise ::Zip::Error, 'Truncated local zip entry header' + else + if @extra.is_a?(::Zip::ExtraField) + @extra.merge(extra) if extra + else + @extra = ::Zip::ExtraField.new(extra) + end + end + parse_zip64_extra(true) + @local_header_size = calculate_local_header_size + end + + def pack_local_entry + zip64 = @extra['Zip64'] + [::Zip::LOCAL_ENTRY_SIGNATURE, + @version_needed_to_extract, # version needed to extract + @gp_flags, # @gp_flags + @compression_method, + @time.to_binary_dos_time, # @last_mod_time + @time.to_binary_dos_date, # @last_mod_date + @crc, + zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size, + zip64 && zip64.original_size ? 0xFFFFFFFF : @size, + name_size, + @extra ? @extra.local_size : 0].pack('VvvvvvVVVvv') + end + + def write_local_entry(io, rewrite = false) #:nodoc:all + prep_zip64_extra(true) + verify_local_header_size! if rewrite + @local_header_offset = io.tell + + io << pack_local_entry + + io << @name + io << @extra.to_local_bin if @extra + @local_header_size = io.tell - @local_header_offset + end + + def unpack_c_dir_entry(buf) + @header_signature, + @version, # version of encoding software + @fstype, # filesystem type + @version_needed_to_extract, + @gp_flags, + @compression_method, + @last_mod_time, + @last_mod_date, + @crc, + @compressed_size, + @size, + @name_length, + @extra_length, + @comment_length, + _, # diskNumberStart + @internal_file_attributes, + @external_file_attributes, + @local_header_offset, + @name, + @extra, + @comment = buf.unpack('VCCvvvvvVVVvvvvvVV') + end + + def set_ftype_from_c_dir_entry + @ftype = case @fstype + when ::Zip::FSTYPE_UNIX + @unix_perms = (@external_file_attributes >> 16) & 0o7777 + case (@external_file_attributes >> 28) + when ::Zip::FILE_TYPE_DIR + :directory + when ::Zip::FILE_TYPE_FILE + :file + 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 + if name_is_directory? + :directory + else + :file + end + end + else + if name_is_directory? + :directory + else + :file + end + end + end + + 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 + + def read_c_dir_extra_field(io) + if @extra.is_a?(::Zip::ExtraField) + @extra.merge(io.read(@extra_length)) + else + @extra = ::Zip::ExtraField.new(io.read(@extra_length)) + end + end + + def read_c_dir_entry(io) #:nodoc:all + 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) + 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 + read_c_dir_extra_field(io) + @comment = io.read(@comment_length) + check_c_dir_entry_comment_size + set_ftype_from_c_dir_entry + parse_zip64_extra(false) + end + + def file_stat(path) # :nodoc: + if @follow_symlinks + ::File.stat(path) + else + ::File.lstat(path) + end + end + + 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 + @unix_perms = stat.mode & 0o7777 + end + + def set_unix_permissions_on_path(dest_path) + # BUG: does not update timestamps into account + # 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() + end + + def set_extra_attributes_on_path(dest_path) # :nodoc: + return unless file? || directory? + + case @fstype + when ::Zip::FSTYPE_UNIX + set_unix_permissions_on_path(dest_path) + end + end + + def pack_c_dir_entry + zip64 = @extra['Zip64'] + [ + @header_signature, + @version, # version of encoding software + @fstype, # filesystem type + @version_needed_to_extract, # @versionNeededToExtract + @gp_flags, # @gp_flags + @compression_method, + @time.to_binary_dos_time, # @last_mod_time + @time.to_binary_dos_date, # @last_mod_date + @crc, + zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size, + zip64 && zip64.original_size ? 0xFFFFFFFF : @size, + name_size, + @extra ? @extra.c_dir_size : 0, + comment_size, + zip64 && zip64.disk_start_number ? 0xFFFF : 0, # disk number start + @internal_file_attributes, # file type (binary=0, text=1) + @external_file_attributes, # native filesystem attributes + zip64 && zip64.relative_header_offset ? 0xFFFFFFFF : @local_header_offset, + @name, + @extra, + @comment + ].pack('VCCvvvvvVVVvvvvvVV') + end + + def write_c_dir_entry(io) #:nodoc:all + prep_zip64_extra(false) + case @fstype + when ::Zip::FSTYPE_UNIX + ft = case @ftype + when :file + @unix_perms ||= 0o644 + ::Zip::FILE_TYPE_FILE + when :directory + @unix_perms ||= 0o755 + ::Zip::FILE_TYPE_DIR + when :symlink + @unix_perms ||= 0o755 + ::Zip::FILE_TYPE_SYMLINK + end + + unless ft.nil? + @external_file_attributes = (ft << 12 | (@unix_perms & 0o7777)) << 16 + end + end + + io << pack_c_dir_entry + + io << @name + io << (@extra ? @extra.to_c_dir_bin : '') + io << @comment + end + + 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) + end + keys_equal && time.dos_equals(other.time) + end + + def <=>(other) + to_s <=> other.to_s + end + + # Returns an IO like object for the given ZipEntry. + # Warning: may behave weird with symlinks. + def get_input_stream(&block) + if @ftype == :directory + yield ::Zip::NullInputStream if block_given? + ::Zip::NullInputStream + elsif @filepath + case @ftype + when :file + ::File.open(@filepath, 'rb', &block) + when :symlink + linkpath = ::File.readlink(@filepath) + stringio = ::StringIO.new(linkpath) + yield(stringio) if block_given? + stringio + else + raise "unknown @file_type #{@ftype}" + end + else + zis = ::Zip::InputStream.new(@zipfile, local_header_offset) + zis.instance_variable_set(:@complete_entry, self) + zis.get_next_entry + if block_given? + begin + yield(zis) + ensure + zis.close + end + else + zis + end + end + end + + def gather_fileinfo_from_srcpath(src_path) # :nodoc: + stat = file_stat(src_path) + @ftype = case stat.ftype + when 'file' + if name_is_directory? + raise ArgumentError, + "entry name '#{newEntry}' indicates directory entry, but " \ + "'#{src_path}' is not a directory" + end + :file + when 'directory' + @name += '/' unless name_is_directory? + :directory + when 'link' + if name_is_directory? + raise ArgumentError, + "entry name '#{newEntry}' indicates directory entry, but " \ + "'#{src_path}' is not a directory" + end + :symlink + else + raise "unknown file type: #{src_path.inspect} #{stat.inspect}" + end + + @filepath = src_path + get_extra_attributes_from_path(@filepath) + end + + 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) + get_input_stream { |is| ::Zip::IOExtras.copy_stream(zip_output_stream, is) } + else + zip_output_stream.copy_raw_entry(self) + end + end + + def parent_as_string + 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) + if @zipfile.respond_to?(:seek) && @zipfile.respond_to?(:read) + yield @zipfile + else + ::File.open(@zipfile, 'rb', &block) + end + end + + def clean_up + # By default, do nothing + end + + private + + 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 + end + + 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" + 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 + 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" + if ::Zip.validate_entry_sizes + raise ::Zip::EntrySizeError, message + else + puts "WARNING: #{message}" + warned = true + end + end + end + end + end + end + + 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 + end + ::FileUtils.mkdir_p(dest_path) + set_extra_attributes_on_path(dest_path) + end + + # BUG: create_symlink() does not use &block + 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}" + end + + # 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? + 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) + end + end + + def data_descriptor_size + (@gp_flags & 0x0008) > 0 ? 16 : 0 + 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 + need_zip64 = @size >= 0xFFFFFFFF || @compressed_size >= 0xFFFFFFFF + need_zip64 ||= @local_header_offset >= 0xFFFFFFFF unless for_local_header + if need_zip64 + @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') + + # 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 + end + end + end +end + +# Copyright (C) 2002, 2003 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/lib/zip/entry_set.rb b/lib/zip/entry_set.rb new file mode 100644 index 00000000..3272b2a4 --- /dev/null +++ b/lib/zip/entry_set.rb @@ -0,0 +1,86 @@ +module Zip + class EntrySet #:nodoc:all + include Enumerable + attr_accessor :entry_set, :entry_order + + def initialize(an_enumerable = []) + super() + @entry_set = {} + an_enumerable.each { |o| push(o) } + end + + def include?(entry) + @entry_set.include?(to_key(entry)) + end + + def find_entry(entry) + @entry_set[to_key(entry)] + end + + def <<(entry) + @entry_set[to_key(entry)] = entry if entry + end + + alias push << + + def size + @entry_set.size + end + + alias length size + + 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 + end + + def entries + sorted_entries.values + end + + # deep clone + def dup + EntrySet.new(@entry_set.values.map(&:dup)) + end + + def ==(other) + return false unless other.kind_of?(EntrySet) + @entry_set.values == other.entry_set.values + end + + def parent(entry) + @entry_set[to_key(entry.parent_as_string)] + end + + 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 + end + + protected + + def sorted_entries + ::Zip.sort_entries ? Hash[@entry_set.sort] : @entry_set + end + + private + + def to_key(entry) + k = entry.to_s.chomp('/') + k.downcase! if ::Zip.case_insensitive_match + k + end + end +end + +# Copyright (C) 2002, 2003 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/lib/zip/errors.rb b/lib/zip/errors.rb new file mode 100644 index 00000000..364c6eee --- /dev/null +++ b/lib/zip/errors.rb @@ -0,0 +1,18 @@ +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 + + # Backwards compatibility with v1 (delete in v2) + ZipError = Error + ZipEntryExistsError = EntryExistsError + ZipDestinationFileExistsError = DestinationFileExistsError + ZipCompressionMethodError = CompressionMethodError + ZipEntryNameError = EntryNameError + ZipInternalError = InternalError +end diff --git a/lib/zip/extra_field.rb b/lib/zip/extra_field.rb new file mode 100644 index 00000000..72c36764 --- /dev/null +++ b/lib/zip/extra_field.rb @@ -0,0 +1,101 @@ +module Zip + class ExtraField < Hash + ID_MAP = {} + + def initialize(binstr = nil) + merge(binstr) if binstr + end + + def extra_field_type_exist(binstr, id, len, i) + field_name = ID_MAP[id].name + if member?(field_name) + self[field_name].merge(binstr[i, len + 4]) + else + field_obj = ID_MAP[id].new(binstr[i, len + 4]) + self[field_name] = field_obj + end + end + + def extra_field_type_unknown(binstr, len, i) + create_unknown_item unless self['Unknown'] + if !len || len + 4 > binstr[i..-1].bytesize + self['Unknown'] << binstr[i..-1] + return + end + self['Unknown'] << binstr[i, len + 4] + end + + def create_unknown_item + s = ''.dup + class << s + alias_method :to_c_dir_bin, :to_s + alias_method :to_local_bin, :to_s + end + self['Unknown'] = s + end + + def merge(binstr) + return if binstr.empty? + i = 0 + while i < binstr.bytesize + id = binstr[i, 2] + len = binstr[i + 2, 2].to_s.unpack('v').first + 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) + end + i += len + 4 + end + end + + 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 + + # 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) } + result + end + + def to_local_bin + ordered_values.map! { |v| v.to_local_bin.force_encoding('BINARY') }.join + end + + alias to_s to_local_bin + + def to_c_dir_bin + ordered_values.map! { |v| v.to_c_dir_bin.force_encoding('BINARY') }.join + end + + def c_dir_size + to_c_dir_bin.bytesize + end + + def local_size + to_local_bin.bytesize + end + + alias length local_size + alias size local_size + end +end + +require 'zip/extra_field/generic' +require 'zip/extra_field/universal_time' +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 +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/lib/zip/extra_field/generic.rb b/lib/zip/extra_field/generic.rb new file mode 100644 index 00000000..5931b5c2 --- /dev/null +++ b/lib/zip/extra_field/generic.rb @@ -0,0 +1,43 @@ +module Zip + class ExtraField::Generic + def self.register_map + if const_defined?(:HEADER_ID) + ::Zip::ExtraField::ID_MAP[const_get(:HEADER_ID)] = self + end + end + + def self.name + @name ||= to_s.split('::')[-1] + end + + # 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) + $stderr.puts 'Warning: weired extra feild header ID. skip parsing' + 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 + true + end + + def to_local_bin + s = pack_for_local + 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 + end + end +end diff --git a/lib/zip/extra_field/ntfs.rb b/lib/zip/extra_field/ntfs.rb new file mode 100644 index 00000000..687704d8 --- /dev/null +++ b/lib/zip/extra_field/ntfs.rb @@ -0,0 +1,90 @@ +module Zip + # PKWARE NTFS Extra Field (0x000a) + # Only Tag 0x0001 is supported + class ExtraField::NTFS < ExtraField::Generic + HEADER_ID = [0x000A].pack('v') + register_map + + WINDOWS_TICK = 10_000_000.0 + SEC_TO_UNIX_EPOCH = 11_644_473_600 + + def initialize(binstr = nil) + @ctime = nil + @mtime = nil + @atime = nil + binstr && merge(binstr) + end + + attr_accessor :atime, :ctime, :mtime + + def merge(binstr) + return if binstr.empty? + size, content = initial_parse(binstr) + (size && content) || return + + content = content[4..-1] + tags = parse_tags(content) + + tag1 = tags[1] + return unless tag1 + ntfs_mtime, ntfs_atime, ntfs_ctime = tag1.unpack('Qmy.zip + # (creating it if it doesn't exist) and adds an entry + # first.txt and a directory entry a_dir + # to it. + # + # require 'zip' + # + # Zip::File.open("my.zip", Zip::File::CREATE) { + # |zipfile| + # zipfile.get_output_stream("first.txt") { |f| f.puts "Hello from ZipFile" } + # zipfile.mkdir("a_dir") + # } + # + # The next example reopens my.zip writes the contents of + # first.txt to standard out and deletes the entry from + # the archive. + # + # require 'zip' + # + # Zip::File.open("my.zip", Zip::File::CREATE) { + # |zipfile| + # puts zipfile.read("first.txt") + # zipfile.remove("first.txt") + # } + # + # ZipFileSystem offers an alternative API that emulates ruby's + # interface for accessing the filesystem, ie. the File and Dir classes. + + class File < CentralDirectory + 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, :close] + + attr_reader :name + + # default -> false + attr_accessor :restore_ownership + # default -> false + attr_accessor :restore_permissions + # default -> true + attr_accessor :restore_times + # Returns the zip files comment, if it has one + attr_accessor :comment + + # Opens a zip archive. Pass true as the second parameter to create + # a new archive if it doesn't exist already. + def initialize(path_or_io, create = false, buffer = false, options = {}) + super() + @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 ::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 + 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 + @restore_permissions = options[:restore_permissions] || true + @restore_times = options[:restore_times] || true + 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. + def open(file_name, create = false) + zf = ::Zip::File.new(file_name, create) + return zf unless block_given? + begin + yield zf + ensure + zf.close + 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, true, 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 = {}) + 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 + + 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? + yield zf + + begin + zf.write_buffer(io) + rescue IOError => e + raise unless e.message == 'not opened for writing' + end + end + + # Iterates over the contents of the ZipFile. This is more efficient + # than using a ZipInputStream since this methods simply iterates + # through the entries in the central directory structure in the archive + # 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) + open(aZipFileName) do |zipFile| + zipFile.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) + # Checking for correct zip structure + 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| + 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 + # 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) + end + + # Returns an output stream to the specified entry. If entry is not an instance + # of Zip::Entry, a new Zip::Entry will be initialized using the arguments + # 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) + new_entry = + if entry.kind_of?(Entry) + entry + else + Entry.new(@name, entry.to_s, comment, extra, compressed_size, crc, compression_method, size, time) + end + if new_entry.directory? + raise ArgumentError, + "cannot open stream to directory entry - '#{new_entry}'" + end + 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) + end + + # Returns the name of the zip archive + def to_s + @name + end + + # Returns a string containing the contents of the specified entry + def read(entry) + get_input_stream(entry) { |is| is.read } + end + + # Convenience method for adding the contents of a file to the archive + 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.gather_fileinfo_from_srcpath(src_path) + new_entry.dirty = true + @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)) + end + + # Renames the specified entry. + def rename(entry, new_name, &continue_on_exists_proc) + foundEntry = get_entry(entry) + check_entry_exists(new_name, continue_on_exists_proc, 'rename') + @entry_set.delete(foundEntry) + foundEntry.name = new_name + @entry_set << foundEntry + end + + # Replaces the specified entry with the contents of srcPath (from + # the file system). + def replace(entry, srcPath) + check_file(srcPath) + remove(entry) + add(entry, srcPath) + end + + # Extracts entry to file dest_path. + def extract(entry, dest_path, &block) + block ||= proc { ::Zip.on_exists_proc } + found_entry = get_entry(entry) + found_entry.extract(dest_path, &block) + end + + # Commits changes that has been made since the previous commit to + # the zip archive. + def commit + return if name.is_a?(StringIO) || !commit_required? + on_success_replace do |tmp_file| + ::Zip::OutputStream.open(tmp_file) do |zos| + @entry_set.each do |e| + e.write_to_zip_output_stream(zos) + e.dirty = false + e.clean_up + end + zos.comment = comment + end + true + end + initialize(name) + end + + # Write buffer write changes to buffer and return + 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 + end + end + + # Closes the zip file committing any changes that has been made. + def close + commit + end + + # Returns true if any changes has been made to this archive since + # the previous commit + def commit_required? + @entry_set.each do |e| + return true if e.dirty + end + @comment != @stored_comment || @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) + @entry_set.find_entry(entry_name) + end + + # Searches for entries given a glob + def glob(*args, &block) + @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) + 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 + selected_entry + end + + # Creates a directory + def mkdir(entryName, permissionInt = 0o755) + raise Errno::EEXIST, "File exists - #{entryName}" if find_entry(entryName) + entryName = entryName.dup.to_s + entryName << '/' unless entryName.end_with?('/') + @entry_set << ::Zip::StreamableDirectory.new(@name, entryName, nil, permissionInt) + end + + private + + def directory?(newEntry, srcPath) + srcPathIsDirectory = ::File.directory?(srcPath) + if newEntry.directory? && !srcPathIsDirectory + raise ArgumentError, + "entry name '#{newEntry}' indicates directory entry, but " \ + "'#{srcPath}' is not a directory" + elsif !newEntry.directory? && srcPathIsDirectory + newEntry.name += '/' + end + newEntry.directory? && srcPathIsDirectory + end + + def check_entry_exists(entryName, continue_on_exists_proc, procedureName) + continue_on_exists_proc ||= proc { Zip.continue_on_exists_proc } + return unless @entry_set.include?(entryName) + if continue_on_exists_proc.call + remove get_entry(entryName) + else + raise ::Zip::EntryExistsError, + procedureName + " failed. Entry #{entryName} already exists" + end + end + + def check_file(path) + raise Errno::ENOENT, path unless ::File.readable?(path) + end + + 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) + end + end + end + end +end + +# Copyright (C) 2002, 2003 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/lib/zip/zipfilesystem.rb b/lib/zip/filesystem.rb old mode 100755 new mode 100644 similarity index 63% rename from lib/zip/zipfilesystem.rb rename to lib/zip/filesystem.rb index ce2bdf1b..81ad1a18 --- a/lib/zip/zipfilesystem.rb +++ b/lib/zip/filesystem.rb @@ -1,41 +1,39 @@ -require 'zip/zip' +require 'zip' module Zip - - # The ZipFileSystem API provides an API for accessing entries in - # a zip archive that is similar to ruby's builtin File and Dir + # The ZipFileSystem API provides an API for accessing entries in + # a zip archive that is similar to ruby's builtin File and Dir # classes. # - # Requiring 'zip/zipfilesystem' includes this module in ZipFile - # making the methods in this module available on ZipFile objects. + # Requiring 'zip/filesystem' includes this module in Zip::File + # making the methods in this module available on Zip::File objects. # - # Using this API the following example creates a new zip file + # Using this API the following example creates a new zip file # my.zip containing a normal entry with the name # first.txt, a directory entry named mydir # and finally another normal entry named second.txt # - # require 'zip/zipfilesystem' - # - # Zip::ZipFile.open("my.zip", Zip::ZipFile::CREATE) { + # require 'zip/filesystem' + # + # Zip::File.open("my.zip", Zip::File::CREATE) { # |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" } # } # - # Reading is as easy as writing, as the following example shows. The + # 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/zipfilesystem' - # - # Zip::ZipFile.open("my.zip") { + # require 'zip/filesystem' + # + # Zip::File.open("my.zip") { # |zipfile| # puts zipfile.file.read("first.txt") # } - module ZipFileSystem - + module FileSystem def initialize # :nodoc: mappedZip = ZipFileNameMapper.new(self) @zipFsDir = ZipFsDir.new(mappedZip) @@ -45,44 +43,40 @@ def initialize # :nodoc: end # Returns a ZipFsDir which is much like ruby's builtin Dir (class) - # object, except it works on the ZipFile on which this method is + # object, except it works on the Zip::File on which this method is # invoked def dir @zipFsDir end # Returns a ZipFsFile which is much like ruby's builtin File (class) - # object, except it works on the ZipFile on which this method is + # object, except it works on the Zip::File on which this method is # invoked def file @zipFsFile end # Instances of this class are normally accessed via the accessor - # ZipFile::file. An instance of ZipFsFile behaves like ruby's - # builtin File (class) object, except it works on ZipFile entries. + # 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 -# protected :dir + # protected :dir class ZipFsStat - class << self - def delegate_to_fs_file(*methods) methods.each do |method| - self.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 end - end def initialize(zipFsFile, entryName) @@ -91,15 +85,17 @@ def initialize(zipFsFile, entryName) end def kind_of?(t) - super || t == ::File::Stat + super || t == ::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 + :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 blocks + nil + end def get_entry @zipFsFile.__send__(:get_entry, @entryName) @@ -108,8 +104,8 @@ def get_entry def gid e = get_entry - if e.extra.member? "IUnix" - e.extra["IUnix"].gid || 0 + if e.extra.member? 'IUnix' + e.extra['IUnix'].gid || 0 else 0 end @@ -117,43 +113,57 @@ def gid def uid e = get_entry - if e.extra.member? "IUnix" - e.extra["IUnix"].uid || 0 + if e.extra.member? 'IUnix' + e.extra['IUnix'].uid || 0 else 0 end end - def ino; 0; end + def ino + 0 + end - def dev; 0; end + def dev + 0 + end - def rdev; 0; end + def rdev + 0 + end - def rdev_major; 0; end + def rdev_major + 0 + end - def rdev_minor; 0; end + def rdev_minor + 0 + end def ftype if file? - return "file" + 'file' elsif directory? - return "directory" + 'directory' else - raise StandardError, "Unknown file type" + raise StandardError, 'Unknown file type' end end - def nlink; 1; end + def nlink + 1 + end - def blksize; nil; end + def blksize + nil + end def mode e = get_entry if e.fstype == 3 - e.externalFileAttributes >> 16 + e.external_file_attributes >> 16 else - 33206 # 33206 is equivalent to -rw-rw-rw- + 33_206 # 33206 is equivalent to -rw-rw-rw- end end end @@ -163,7 +173,7 @@ def initialize(mappedZip) end def get_entry(fileName) - if ! exists?(fileName) + unless exists?(fileName) raise Errno::ENOENT, "No such file or directory - #{fileName}" end @mappedZip.find_entry(fileName) @@ -171,77 +181,75 @@ def get_entry(fileName) private :get_entry def unix_mode_cmp(fileName, mode) - begin - e = get_entry(fileName) - e.fstype == 3 && ((e.externalFileAttributes >> 16) & mode ) != 0 - rescue Errno::ENOENT - false - end + 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 + expand_path(fileName) == '/' || !@mappedZip.find_entry(fileName).nil? end - alias :exist? :exists? + alias exist? exists? # Permissions not implemented, so if the file exists it is accessible - alias owned? exists? - alias grpowned? exists? + alias owned? exists? + alias grpowned? exists? def readable?(fileName) - unix_mode_cmp(fileName, 0444) + unix_mode_cmp(fileName, 0o444) end - alias readable_real? readable? + alias readable_real? readable? def writable?(fileName) - unix_mode_cmp(fileName, 0222) + unix_mode_cmp(fileName, 0o222) end - alias writable_real? writable? + alias writable_real? writable? def executable?(fileName) - unix_mode_cmp(fileName, 0111) + unix_mode_cmp(fileName, 0o111) end alias executable_real? executable? def setuid?(fileName) - unix_mode_cmp(fileName, 04000) + unix_mode_cmp(fileName, 0o4000) end def setgid?(fileName) - unix_mode_cmp(fileName, 02000) + unix_mode_cmp(fileName, 0o2000) end def sticky?(fileName) - unix_mode_cmp(fileName, 01000) + unix_mode_cmp(fileName, 0o1000) end def umask(*args) ::File.umask(*args) end - def truncate(fileName, len) - raise StandardError, "truncate not supported" + 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?) + expand_path(fileName) == '/' || (!entry.nil? && entry.directory?) end - def open(fileName, openMode = "r", permissionInt = 0644, &block) - openMode.gsub!("b", "") # ignore b option + def open(fileName, openMode = 'r', permissionInt = 0o644, &block) + openMode.delete!('b') # ignore b option case openMode - when "r" - @mappedZip.get_input_stream(fileName, &block) - when "w" - @mappedZip.get_output_stream(fileName, permissionInt, &block) - else - raise StandardError, "openmode '#{openMode} not supported" unless openMode == "r" + when 'r' + @mappedZip.get_input_stream(fileName, &block) + when 'w' + @mappedZip.get_output_stream(fileName, permissionInt, &block) + else + raise StandardError, "openmode '#{openMode} not supported" unless openMode == 'r' end end - def new(fileName, openMode = "r") + def new(fileName, openMode = 'r') open(fileName, openMode) end @@ -252,43 +260,41 @@ def size(fileName) # Returns nil for not found and nil for directories def size?(fileName) entry = @mappedZip.find_entry(fileName) - return (entry == nil || entry.directory?) ? nil : entry.size + entry.nil? || entry.directory? ? nil : entry.size end def chown(ownerInt, groupInt, *filenames) - filenames.each { |fileName| + filenames.each do |fileName| e = get_entry(fileName) - unless e.extra.member?("IUnix") - e.extra.create("IUnix") - end - e.extra["IUnix"].uid = ownerInt - e.extra["IUnix"].gid = groupInt - } + e.extra.create('IUnix') unless e.extra.member?('IUnix') + e.extra['IUnix'].uid = ownerInt + e.extra['IUnix'].gid = groupInt + end filenames.size end - def chmod (modeInt, *filenames) - filenames.each { |fileName| + def chmod(modeInt, *filenames) + filenames.each do |fileName| e = get_entry(fileName) e.fstype = 3 # force convertion filesystem type to unix e.unix_perms = modeInt - e.externalFileAttributes = modeInt << 16 + e.external_file_attributes = modeInt << 16 e.dirty = true - } + end filenames.size end def zero?(fileName) sz = size(fileName) - sz == nil || sz == 0 + sz.nil? || sz == 0 rescue Errno::ENOENT false end def file?(fileName) entry = @mappedZip.find_entry(fileName) - entry != nil && entry.file? - end + !entry.nil? && entry.file? + end def dirname(fileName) ::File.dirname(fileName) @@ -307,9 +313,9 @@ def join(*fragments) end def utime(modifiedTime, *fileNames) - fileNames.each { |fileName| + fileNames.each do |fileName| get_entry(fileName).time = modifiedTime - } + end end def mtime(fileName) @@ -318,66 +324,64 @@ def mtime(fileName) def atime(fileName) e = get_entry(fileName) - if e.extra.member? "UniversalTime" - e.extra["UniversalTime"].atime - else - nil + 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 - else - nil + if e.extra.member? 'UniversalTime' + e.extra['UniversalTime'].ctime + elsif e.extra.member? 'NTFS' + e.extra['NTFS'].ctime end end - def pipe?(filename) + def pipe?(_filename) false end - def blockdev?(filename) + def blockdev?(_filename) false end - def chardev?(filename) + 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" + @mappedZip.get_entry(fileName).directory? ? 'directory' : 'file' end - def readlink(fileName) - raise NotImplementedError, "The readlink() function is not implemented" + def readlink(_fileName) + raise NotImplementedError, 'The readlink() function is not implemented' end - def symlink(fileName, symlinkName) - raise NotImplementedError, "The symlink() function is not implemented" + def symlink(_fileName, _symlinkName) + raise NotImplementedError, 'The symlink() function is not implemented' end - def link(fileName, symlinkName) - raise NotImplementedError, "The link() function is not implemented" + def link(_fileName, _symlinkName) + raise NotImplementedError, 'The link() function is not implemented' end def pipe - raise NotImplementedError, "The pipe() function is not implemented" + raise NotImplementedError, 'The pipe() function is not implemented' end def stat(fileName) - if ! exists?(fileName) - raise Errno::ENOENT, fileName - end + raise Errno::ENOENT, fileName unless exists?(fileName) ZipFsStat.new(self, fileName) end @@ -392,7 +396,7 @@ def read(fileName) end def popen(*args, &aProc) - File.popen(*args, &aProc) + ::File.popen(*args, &aProc) end def foreach(fileName, aSep = $/, &aProc) @@ -400,20 +404,19 @@ def foreach(fileName, aSep = $/, &aProc) end def delete(*args) - args.each { - |fileName| + args.each do |fileName| if directory?(fileName) raise Errno::EISDIR, "Is a directory - \"#{fileName}\"" end @mappedZip.remove(fileName) - } + end end def rename(fileToRename, newName) @mappedZip.rename(fileToRename, newName) { true } end - alias :unlink :delete + alias unlink delete def expand_path(aPath) @mappedZip.expand_path(aPath) @@ -427,7 +430,6 @@ 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 end @@ -451,7 +453,9 @@ def open(aDirectoryName) dirIt end - def pwd; @mappedZip.pwd; end + def pwd + @mappedZip.pwd + end alias getwd pwd def chdir(aDirectoryName) @@ -467,8 +471,8 @@ def entries(aDirectoryName) entries end - def glob(*args,&block) - @mappedZip.glob(*args,&block) + def glob(*args, &block) + @mappedZip.glob(*args, &block) end def foreach(aDirectoryName) @@ -479,11 +483,10 @@ def foreach(aDirectoryName) path << '/' unless path.end_with?('/') path = Regexp.escape(path) subDirEntriesRegex = Regexp.new("^#{path}([^/]+)$") - @mappedZip.each { - |fileName| + @mappedZip.each do |fileName| match = subDirEntriesRegex.match(fileName) - yield(match[1]) unless match == nil - } + yield(match[1]) unless match.nil? + end end def delete(entryName) @@ -492,17 +495,16 @@ def delete(entryName) end @mappedZip.remove(entryName) end - alias rmdir delete + alias rmdir delete alias unlink delete - def mkdir(entryName, permissionInt = 0755) + def mkdir(entryName, permissionInt = 0o755) @mappedZip.mkdir(entryName, permissionInt) end - def chroot(*args) - raise NotImplementedError, "The chroot() function is not implemented" + def chroot(*_args) + raise NotImplementedError, 'The chroot() function is not implemented' end - end class ZipFsDirIterator # :nodoc:all @@ -518,39 +520,39 @@ def close end def each(&aProc) - raise IOError, "closed directory" if @fileNames == nil + raise IOError, 'closed directory' if @fileNames.nil? @fileNames.each(&aProc) end def read - raise IOError, "closed directory" if @fileNames == nil - @fileNames[(@index+=1)-1] + raise IOError, 'closed directory' if @fileNames.nil? + @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 + raise IOError, 'closed directory' if @fileNames.nil? @index = anIntegerPosition end def tell - raise IOError, "closed directory" if @fileNames == nil + raise IOError, 'closed directory' if @fileNames.nil? @index end end - # All access to ZipFile from ZipFsFile and ZipFsDir goes through a + # 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(zipFile) @zipFile = zipFile - @pwd = "/" + @pwd = '/' end attr_accessor :pwd @@ -571,6 +573,10 @@ def get_output_stream(fileName, permissionInt = nil, &aProc) @zipFile.get_output_stream(expand_to_entry(fileName), permissionInt, &aProc) end + def glob(pattern, *flags, &block) + @zipFile.glob(expand_to_entry(pattern), *flags, &block) + end + def read(fileName) @zipFile.read(expand_to_entry(fileName)) end @@ -580,28 +586,27 @@ def remove(fileName) end def rename(fileName, newName, &continueOnExistsProc) - @zipFile.rename(expand_to_entry(fileName), expand_to_entry(newName), + @zipFile.rename(expand_to_entry(fileName), expand_to_entry(newName), &continueOnExistsProc) end - def mkdir(fileName, permissionInt = 0755) + def mkdir(fileName, permissionInt = 0o755) @zipFile.mkdir(expand_to_entry(fileName), permissionInt) end # Turns entries into strings and adds leading / # and removes trailing slash on directories def each - @zipFile.each { - |e| - yield("/"+e.to_s.chomp("/")) - } + @zipFile.each do |e| + yield('/' + e.to_s.chomp('/')) + end end def expand_path(aPath) - expanded = aPath.start_with?("/") ? aPath : ::File.join(@pwd, aPath) - expanded.gsub!(/\/\.(\/|$)/, "") - expanded.gsub!(/[^\/]+\/\.\.(\/|$)/, "") - expanded.empty? ? "/" : expanded + expanded = aPath.start_with?('/') ? aPath : ::File.join(@pwd, aPath) + expanded.gsub!(/\/\.(\/|$)/, '') + expanded.gsub!(/[^\/]+\/\.\.(\/|$)/, '') + expanded.empty? ? '/' : expanded end private @@ -612,8 +617,8 @@ def expand_to_entry(aPath) end end - class ZipFile - include ZipFileSystem + class File + include FileSystem end end diff --git a/lib/zip/inflater.rb b/lib/zip/inflater.rb old mode 100755 new mode 100644 index 10685bac..f1b26d45 --- a/lib/zip/inflater.rb +++ b/lib/zip/inflater.rb @@ -1,61 +1,62 @@ module Zip - class Inflater < Decompressor #:nodoc:all - def initialize(inputStream) - super - @zlibInflater = Zlib::Inflate.new(-Zlib::MAX_WBITS) - @outputBuffer="" - @hasReturnedEmptyString = ! EMPTY_FILE_RETURNS_EMPTY_STRING_FIRST + class Inflater < Decompressor #:nodoc:all + def initialize(input_stream, decrypter = NullDecrypter.new) + super(input_stream) + @zlib_inflater = ::Zlib::Inflate.new(-Zlib::MAX_WBITS) + @output_buffer = ''.dup + @has_returned_empty_string = false + @decrypter = decrypter end - def sysread(numberOfBytes = nil, buf = nil) - readEverything = numberOfBytes.nil? - while (readEverything || @outputBuffer.bytesize < numberOfBytes) + 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? - @outputBuffer << internal_produce_input(buf) + @output_buffer << internal_produce_input(buf) end - return value_when_finished if @outputBuffer.bytesize == 0 && input_finished? - endIndex = numberOfBytes.nil? ? @outputBuffer.bytesize : numberOfBytes - return @outputBuffer.slice!(0...endIndex) + return value_when_finished if @output_buffer.bytesize == 0 && input_finished? + end_index = number_of_bytes.nil? ? @output_buffer.bytesize : number_of_bytes + @output_buffer.slice!(0...end_index) end def produce_input - if (@outputBuffer.empty?) - return internal_produce_input + if @output_buffer.empty? + internal_produce_input else - return @outputBuffer.slice!(0...(@outputBuffer.length)) + @output_buffer.slice!(0...(@output_buffer.length)) 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? - @outputBuffer.empty? && internal_input_finished? + @output_buffer.empty? && internal_input_finished? end - alias :eof :input_finished? - alias :eof? :input_finished? + + alias :eof input_finished? + alias :eof? input_finished? private - def internal_produce_input(buf = nil) + def internal_produce_input(buf = '') retried = 0 begin - @zlibInflater.inflate(@inputStream.read(Decompressor::CHUNK_SIZE, buf)) + @zlib_inflater.inflate(@decrypter.decrypt(@input_stream.read(Decompressor::CHUNK_SIZE, buf))) rescue Zlib::BufError - raise if (retried >= 5) # how many times should we retry? + raise if retried >= 5 # how many times should we retry? retried += 1 retry end end def internal_input_finished? - @zlibInflater.finished? + @zlib_inflater.finished? end - # TODO: Specialize to handle different behaviour in ruby > 1.7.0 ? - def value_when_finished # mimic behaviour of ruby File object. - return if @hasReturnedEmptyString - @hasReturnedEmptyString = true - return "" + 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/input_stream.rb b/lib/zip/input_stream.rb new file mode 100644 index 00000000..95fc3c16 --- /dev/null +++ b/lib/zip/input_stream.rb @@ -0,0 +1,173 @@ +module Zip + # InputStream is the basic class for reading zip entries in a + # zip file. It is possible to create a InputStream object directly, + # passing the zip file name to the constructor, but more often than not + # the InputStream will be obtained from a File (perhaps using the + # ZipFileSystem interface) object for a particular entry in the zip + # archive. + # + # A InputStream inherits IOExtras::AbstractInputStream in order + # to provide an IO-like interface for reading from a single zip + # entry. Beyond methods for mimicking an IO-object it contains + # the method get_next_entry for iterating through the entries of + # an archive. get_next_entry returns a Entry object that describes + # the zip entry the InputStream is currently reading from. + # + # Example that creates a zip archive with ZipOutputStream and reads it + # back again with a InputStream. + # + # require 'zip' + # + # Zip::OutputStream.open("my.zip") do |io| + # + # io.put_next_entry("first_entry.txt") + # io.write "Hello world!" + # + # io.put_next_entry("adir/first_entry.txt") + # io.write "Hello again!" + # end + # + # + # Zip::InputStream.open("my.zip") do |io| + # + # while (entry = io.get_next_entry) + # puts "Contents of #{entry.name}: '#{io.read}'" + # end + # end + # + # java.util.zip.ZipInputStream is the original inspiration for this + # class. + + class InputStream + include ::Zip::IOExtras::AbstractInputStream + + # Opens the indicated zip file. An exception is thrown + # if the specified offset in the specified filename is + # not a local zip entry header. + # + # @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) + super() + @archive_io = get_io(context, offset) + @decompressor = ::Zip::NullDecompressor + @decrypter = decrypter || ::Zip::NullDecrypter.new + @current_entry = nil + end + + 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. + def get_next_entry + @archive_io.seek(@current_entry.next_header_offset, IO::SEEK_SET) if @current_entry + open_entry + end + + # 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) + open_entry + end + + # Modeled after IO.sysread + 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 + # returns. + 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 + zio.close if zio + end + end + + def open_buffer(filename_or_io, offset = 0) + puts 'open_buffer is deprecated!!! Use open instead!' + open(filename_or_io, offset) + end + end + + protected + + def get_io(io_or_file, offset = 0) + if io_or_file.respond_to?(:seek) + io = io_or_file.dup + io.seek(offset, ::IO::SEEK_SET) + io + else + file = ::File.open(io_or_file, 'rb') + file.seek(offset, ::IO::SEEK_SET) + file + end + end + + 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) + raise Error, 'password required to decode zip file' + end + if @current_entry && @current_entry.gp_flags & 8 == 8 && @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 + @decompressor = get_decompressor + flush + @current_entry + end + + 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 + ::Zip::PassThruDecompressor.new(@archive_io, @complete_entry.size) + else + ::Zip::PassThruDecompressor.new(@archive_io, @current_entry.size) + end + elsif @current_entry.compression_method == ::Zip::Entry::DEFLATED + header = @archive_io.read(@decrypter.header_bytesize) + @decrypter.reset!(header) + ::Zip::Inflater.new(@archive_io, @decrypter) + else + raise ::Zip::CompressionMethodError, + "Unsupported compression method #{@current_entry.compression_method}" + end + end + + def produce_input + @decompressor.produce_input + end + + def input_finished? + @decompressor.input_finished? + end + end +end + +# Copyright (C) 2002, 2003 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/lib/zip/ioextras.rb b/lib/zip/ioextras.rb old mode 100755 new mode 100644 index b4253040..2412480b --- a/lib/zip/ioextras.rb +++ b/lib/zip/ioextras.rb @@ -1,182 +1,35 @@ -module IOExtras #:nodoc: +module Zip + module IOExtras #:nodoc: + CHUNK_SIZE = 131_072 - CHUNK_SIZE = 131072 + RANGE_ALL = 0..-1 - RANGE_ALL = 0..-1 - - def self.copy_stream(ostream, istream) - s = '' - ostream.write(istream.read(CHUNK_SIZE, s)) until istream.eof? - end - - def self.copy_stream_n(ostream, istream, nbytes) - s = '' - toread = nbytes - while (toread > 0 && ! istream.eof?) - tr = toread > CHUNK_SIZE ? CHUNK_SIZE : toread - ostream.write(istream.read(tr, s)) - toread -= tr - end - end - - - # Implements kind_of? in order to pretend to be an IO object - module FakeIO - def kind_of?(object) - object == IO || super - end - end - - # 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 - include Enumerable - include FakeIO - - def initialize - super - @lineno = 0 - @pos = 0 - @outputBuffer = "" - end - - attr_accessor :lineno - attr_reader :pos - - def read(numberOfBytes = nil, buf = nil) - tbuf = nil - - if @outputBuffer.bytesize > 0 - if numberOfBytes <= @outputBuffer.bytesize - tbuf = @outputBuffer.slice!(0, numberOfBytes) - else - numberOfBytes -= @outputBuffer.bytesize if (numberOfBytes) - rbuf = sysread(numberOfBytes, buf) - tbuf = @outputBuffer - tbuf << rbuf if (rbuf) - @outputBuffer = "" - end - else - tbuf = sysread(numberOfBytes, buf) + class << self + def copy_stream(ostream, istream) + ostream.write(istream.read(CHUNK_SIZE, '')) until istream.eof? end - @pos += tbuf.length - - return nil unless (tbuf) - - if buf - buf.replace(tbuf) - else - buf = tbuf - end - - buf - end - - def readlines(aSepString = $/) - retVal = [] - each_line(aSepString) { |line| retVal << line } - retVal - end - - def gets(aSepString = $/, numberOfBytes = nil) - @lineno = @lineno.next - - if numberOfBytes.respond_to?(:to_int) - numberOfBytes = numberOfBytes.to_int - aSepString = aSepString.to_str if aSepString - elsif aSepString.respond_to?(:to_int) - numberOfBytes = aSepString.to_int - aSepString = $/ - else - numberOfBytes = nil - aSepString = aSepString.to_str if aSepString - end - - return read(numberOfBytes) if aSepString.nil? - aSepString = "#{$/}#{$/}" if aSepString.empty? - - bufferIndex = 0 - overLimit = (numberOfBytes && @outputBuffer.bytesize >= numberOfBytes) - while ((matchIndex = @outputBuffer.index(aSepString, bufferIndex)) == nil && !overLimit) - bufferIndex = [bufferIndex, @outputBuffer.bytesize - aSepString.bytesize].max - if input_finished? - return @outputBuffer.empty? ? nil : flush + 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, '')) + toread -= tr end - @outputBuffer << produce_input - overLimit = (numberOfBytes && @outputBuffer.bytesize >= numberOfBytes) end - sepIndex = [matchIndex + aSepString.bytesize, numberOfBytes || @outputBuffer.bytesize].min - @pos += sepIndex - return @outputBuffer.slice!(0...sepIndex) end - def flush - retVal = @outputBuffer - @outputBuffer="" - return retVal - end - - def readline(aSepString = $/) - retVal = gets(aSepString) - raise EOFError if retVal == nil - retVal - end - - def each_line(aSepString = $/) - while true - yield readline(aSepString) + # Implements kind_of? in order to pretend to be an IO object + module FakeIO + def kind_of?(object) + object == IO || super end - rescue EOFError - end - - alias_method :each, :each_line - end - - - # Implements many of the output convenience methods of IO. - # relies on << - module AbstractOutputStream - include FakeIO - - def write(data) - self << data - data.to_s.bytesize - end - - - def print(*params) - self << params.join($,) << $\.to_s end + end # IOExtras namespace module +end - def printf(aFormatString, *params) - self << sprintf(aFormatString, *params) - end - - def putc(anObject) - self << case anObject - when Fixnum then anObject.chr - when String then anObject - else raise TypeError, "putc: Only Fixnum and String supported" - end - anObject - end - - def puts(*params) - params << "\n" if params.empty? - params.flatten.each do |element| - val = element.to_s - self << val - self << "\n" unless val[-1,1] == "\n" - end - end - - end - -end # IOExtras namespace module - - +require 'zip/ioextras/abstract_input_stream' +require 'zip/ioextras/abstract_output_stream' # Copyright (C) 2002-2004 Thomas Sondergaard # rubyzip is free software; you can redistribute it and/or diff --git a/lib/zip/ioextras/abstract_input_stream.rb b/lib/zip/ioextras/abstract_input_stream.rb new file mode 100644 index 00000000..7b7fd61d --- /dev/null +++ b/lib/zip/ioextras/abstract_input_stream.rb @@ -0,0 +1,111 @@ +module Zip + module IOExtras + # 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 + include Enumerable + include FakeIO + + def initialize + super + @lineno = 0 + @pos = 0 + @output_buffer = '' + end + + attr_accessor :lineno + attr_reader :pos + + def read(number_of_bytes = nil, buf = '') + tbuf = if @output_buffer.bytesize > 0 + if number_of_bytes <= @output_buffer.bytesize + @output_buffer.slice!(0, number_of_bytes) + else + number_of_bytes -= @output_buffer.bytesize if number_of_bytes + rbuf = sysread(number_of_bytes, buf) + out = @output_buffer + out << rbuf if rbuf + @output_buffer = '' + out + end + else + sysread(number_of_bytes, buf) + end + + if tbuf.nil? || tbuf.empty? + return nil if number_of_bytes + return '' + end + + @pos += tbuf.length + + if buf + buf.replace(tbuf) + else + buf = tbuf + end + buf + end + + def readlines(a_sep_string = $/) + ret_val = [] + each_line(a_sep_string) { |line| ret_val << line } + ret_val + end + + def gets(a_sep_string = $/, number_of_bytes = nil) + @lineno = @lineno.next + + if number_of_bytes.respond_to?(:to_int) + number_of_bytes = number_of_bytes.to_int + 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 = $/ + else + number_of_bytes = nil + a_sep_string = a_sep_string.to_str if a_sep_string + end + + return read(number_of_bytes) if a_sep_string.nil? + a_sep_string = "#{$/}#{$/}" if a_sep_string.empty? + + buffer_index = 0 + 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) + end + 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 + + def ungetc(byte) + @output_buffer = byte.chr + @output_buffer + end + + def flush + ret_val = @output_buffer + @output_buffer = '' + ret_val + end + + def readline(a_sep_string = $/) + ret_val = gets(a_sep_string) + raise EOFError unless ret_val + ret_val + end + + def each_line(a_sep_string = $/) + yield readline(a_sep_string) while true + rescue EOFError + end + + alias_method :each, :each_line + end + end +end diff --git a/lib/zip/ioextras/abstract_output_stream.rb b/lib/zip/ioextras/abstract_output_stream.rb new file mode 100644 index 00000000..69d0cc7c --- /dev/null +++ b/lib/zip/ioextras/abstract_output_stream.rb @@ -0,0 +1,43 @@ +module Zip + module IOExtras + # Implements many of the output convenience methods of IO. + # relies on << + module AbstractOutputStream + include FakeIO + + def write(data) + self << data + data.to_s.bytesize + end + + def print(*params) + self << params.join($,) << $\.to_s + end + + def printf(a_format_string, *params) + self << format(a_format_string, *params) + end + + def putc(an_object) + self << case an_object + when Integer + an_object.chr + when String + an_object + else + raise TypeError, 'putc: Only Integer and String supported' + end + an_object + end + + def puts(*params) + params << "\n" if params.empty? + params.flatten.each do |element| + val = element.to_s + self << val + self << "\n" unless val[-1, 1] == "\n" + end + end + end + end +end diff --git a/lib/zip/null_compressor.rb b/lib/zip/null_compressor.rb old mode 100755 new mode 100644 index bb43eeb0..70fd3294 --- a/lib/zip/null_compressor.rb +++ b/lib/zip/null_compressor.rb @@ -2,8 +2,8 @@ module Zip class NullCompressor < Compressor #:nodoc:all include Singleton - def << (data) - raise IOError, "closed stream" + def <<(_data) + raise IOError, 'closed stream' end attr_reader :size, :compressed_size diff --git a/lib/zip/null_decompressor.rb b/lib/zip/null_decompressor.rb old mode 100755 new mode 100644 index 2ce27660..1560ef14 --- a/lib/zip/null_decompressor.rb +++ b/lib/zip/null_decompressor.rb @@ -1,14 +1,15 @@ module Zip - class NullDecompressor #:nodoc:all - include Singleton - def sysread(numberOfBytes = nil, buf = nil) + module NullDecompressor #:nodoc:all + module_function + + def sysread(_numberOfBytes = nil, _buf = nil) nil end - + def produce_input nil end - + def input_finished? true end @@ -16,7 +17,8 @@ def input_finished? def eof true end - alias :eof? :eof + + alias eof? eof end end diff --git a/lib/zip/null_input_stream.rb b/lib/zip/null_input_stream.rb old mode 100755 new mode 100644 index 377f1e45..2cd36616 --- a/lib/zip/null_input_stream.rb +++ b/lib/zip/null_input_stream.rb @@ -1,6 +1,7 @@ module Zip - class NullInputStream < NullDecompressor #:nodoc:all - include IOExtras::AbstractInputStream + module NullInputStream #:nodoc:all + include ::Zip::NullDecompressor + include ::Zip::IOExtras::AbstractInputStream end end diff --git a/lib/zip/output_stream.rb b/lib/zip/output_stream.rb new file mode 100644 index 00000000..d9bbc4df --- /dev/null +++ b/lib/zip/output_stream.rb @@ -0,0 +1,189 @@ +module Zip + # ZipOutputStream is the basic class for writing zip files. It is + # possible to create a ZipOutputStream object directly, passing + # the zip file name to the constructor, but more often than not + # the ZipOutputStream will be obtained from a ZipFile (perhaps using the + # ZipFileSystem interface) object for a particular entry in the zip + # archive. + # + # A ZipOutputStream inherits IOExtras::AbstractOutputStream in order + # to provide an IO-like interface for writing to a single zip + # entry. Beyond methods for mimicking an IO-object it contains + # the method put_next_entry that closes the current entry + # and creates a new. + # + # Please refer to ZipInputStream for example code. + # + # java.util.zip.ZipOutputStream is the original inspiration for this + # class. + + class OutputStream + include ::Zip::IOExtras::AbstractOutputStream + + attr_accessor :comment + + # 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) + super() + @file_name = file_name + @output_stream = if stream + iostream = @file_name.dup + iostream.reopen(@file_name) + iostream.rewind + iostream + else + ::File.new(@file_name, 'wb') + end + @entry_set = ::Zip::EntrySet.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 + # stream is passed to the block and closed when the block + # returns. + class << self + def open(file_name, encrypter = nil) + return new(file_name) unless block_given? + zos = new(file_name, false, encrypter) + yield zos + ensure + zos.close if zos + end + + # Same as #open but writes to a filestream instead + def write_buffer(io = ::StringIO.new(''), encrypter = nil) + zos = new(io, true, encrypter) + yield zos + zos.close_buffer + end + end + + # 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 + @output_stream.close + @closed = true + end + + # 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 + @closed = true + @output_stream + end + + # 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) + 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) + end + new_entry.comment = comment unless comment.nil? + unless extra.nil? + new_entry.extra = extra.is_a?(ExtraField) ? extra : ExtraField.new(extra.to_s) + end + new_entry.compression_method = compression_method unless compression_method.nil? + init_next_entry(new_entry, level) + @current_entry = new_entry + end + + 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 + entry.write_local_entry(@output_stream) + @compressor = NullCompressor.instance + entry.get_raw_input_stream do |is| + is.seek(src_pos, IO::SEEK_SET) + ::Zip::Entry.read_local_entry(is) + IOExtras.copy_stream_n(@output_stream, is, entry.compressed_size) + end + @compressor = NullCompressor.instance + @current_entry = nil + end + + private + + 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 + @current_entry.crc = @compressor.crc + @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 + end + + def init_next_entry(entry, level = Zip.default_compression) + 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) + end + + def get_compressor(entry, level) + case entry.compression_method + when Entry::DEFLATED then + ::Zip::Deflater.new(@output_stream, level, @encrypter) + when Entry::STORED then + ::Zip::PassThruCompressor.new(@output_stream) + else + raise ::Zip::CompressionMethodError, + "Invalid compression method: '#{entry.compression_method}'" + end + end + + 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) + 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 + @compressor.finish + end + + public + + # Modeled after IO.<< + def <<(data) + @compressor << data + self + end + end +end + +# Copyright (C) 2002, 2003 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/lib/zip/pass_thru_compressor.rb b/lib/zip/pass_thru_compressor.rb old mode 100755 new mode 100644 index d0be37cb..fdca2481 --- a/lib/zip/pass_thru_compressor.rb +++ b/lib/zip/pass_thru_compressor.rb @@ -2,16 +2,16 @@ module Zip class PassThruCompressor < Compressor #:nodoc:all def initialize(outputStream) super() - @outputStream = outputStream - @crc = Zlib::crc32 + @output_stream = outputStream + @crc = Zlib.crc32 @size = 0 end - - def << (data) + + def <<(data) val = data.to_s - @crc = Zlib::crc32(val, @crc) + @crc = Zlib.crc32(val, @crc) @size += val.bytesize - @outputStream << val + @output_stream << val end attr_reader :size, :crc diff --git a/lib/zip/pass_thru_decompressor.rb b/lib/zip/pass_thru_decompressor.rb old mode 100755 new mode 100644 index f9f6554b..485462c5 --- a/lib/zip/pass_thru_decompressor.rb +++ b/lib/zip/pass_thru_decompressor.rb @@ -1,37 +1,37 @@ module Zip - class PassThruDecompressor < Decompressor #:nodoc:all - def initialize(inputStream, charsToRead) - super inputStream - @charsToRead = charsToRead - @readSoFar = 0 - @hasReturnedEmptyString = ! EMPTY_FILE_RETURNS_EMPTY_STRING_FIRST + class PassThruDecompressor < Decompressor #:nodoc:all + def initialize(input_stream, chars_to_read) + super(input_stream) + @chars_to_read = chars_to_read + @read_so_far = 0 + @has_returned_empty_string = false end - # TODO: Specialize to handle different behaviour in ruby > 1.7.0 ? - def sysread(numberOfBytes = nil, buf = nil) + def sysread(number_of_bytes = nil, buf = '') if input_finished? - hasReturnedEmptyStringVal = @hasReturnedEmptyString - @hasReturnedEmptyString = true - return "" unless hasReturnedEmptyStringVal + has_returned_empty_string_val = @has_returned_empty_string + @has_returned_empty_string = true + return '' unless has_returned_empty_string_val return end - if (numberOfBytes == nil || @readSoFar + numberOfBytes > @charsToRead) - numberOfBytes = @charsToRead - @readSoFar + if number_of_bytes.nil? || @read_so_far + number_of_bytes > @chars_to_read + number_of_bytes = @chars_to_read - @read_so_far end - @readSoFar += numberOfBytes - @inputStream.read(numberOfBytes, buf) + @read_so_far += number_of_bytes + @input_stream.read(number_of_bytes, buf) end def produce_input - sysread(Decompressor::CHUNK_SIZE) + sysread(::Zip::Decompressor::CHUNK_SIZE) end def input_finished? - (@readSoFar >= @charsToRead) + @read_so_far >= @chars_to_read end - alias :eof :input_finished? - alias :eof? :input_finished? + + alias eof input_finished? + alias eof? input_finished? end end diff --git a/lib/zip/settings.rb b/lib/zip/settings.rb deleted file mode 100644 index b2b0ab2f..00000000 --- a/lib/zip/settings.rb +++ /dev/null @@ -1,10 +0,0 @@ -module Zip - class << self - def options - @options ||= { - :on_exists_proc => false, - :continue_on_exists_proc => false - } - end - end -end diff --git a/lib/zip/zip_streamable_directory.rb b/lib/zip/streamable_directory.rb old mode 100755 new mode 100644 similarity index 65% rename from lib/zip/zip_streamable_directory.rb rename to lib/zip/streamable_directory.rb index c6952bba..4560663c --- a/lib/zip/zip_streamable_directory.rb +++ b/lib/zip/streamable_directory.rb @@ -1,11 +1,11 @@ module Zip - class ZipStreamableDirectory < ZipEntry + class StreamableDirectory < Entry def initialize(zipfile, entry, srcPath = nil, permissionInt = 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(srcPath) if srcPath + @unix_perms = permissionInt if permissionInt end end end diff --git a/lib/zip/zip_streamable_stream.rb b/lib/zip/streamable_stream.rb old mode 100755 new mode 100644 similarity index 56% rename from lib/zip/zip_streamable_stream.rb rename to lib/zip/streamable_stream.rb index dec5aad0..642ddae2 --- a/lib/zip/zip_streamable_stream.rb +++ b/lib/zip/streamable_stream.rb @@ -1,43 +1,47 @@ module Zip - class ZipStreamableStream < DelegateClass(ZipEntry) #nodoc:all + class StreamableStream < DelegateClass(Entry) # nodoc:all def initialize(entry) super(entry) - @tempFile = Tempfile.new(::File.basename(name), ::File.dirname(zipfile)) - @tempFile.binmode + @temp_file = Tempfile.new(::File.basename(name)) + @temp_file.binmode end def get_output_stream if block_given? begin - yield(@tempFile) + yield(@temp_file) ensure - @tempFile.close + @temp_file.close end else - @tempFile + @temp_file end end def get_input_stream - if ! @tempFile.closed? + unless @temp_file.closed? raise StandardError, "cannot open entry for reading while its open for writing - #{name}" end - @tempFile.open # reopens tempfile from top - @tempFile.binmode + @temp_file.open # reopens tempfile from top + @temp_file.binmode if block_given? begin - yield(@tempFile) + yield(@temp_file) ensure - @tempFile.close + @temp_file.close end else - @tempFile + @temp_file end end - + def write_to_zip_output_stream(aZipOutputStream) aZipOutputStream.put_next_entry(self) - get_input_stream { |is| IOExtras.copy_stream(aZipOutputStream, is) } + get_input_stream { |is| ::Zip::IOExtras.copy_stream(aZipOutputStream, is) } + end + + def clean_up + @temp_file.unlink end end end diff --git a/lib/zip/tempfile_bugfixed.rb b/lib/zip/tempfile_bugfixed.rb deleted file mode 100755 index cb69553c..00000000 --- a/lib/zip/tempfile_bugfixed.rb +++ /dev/null @@ -1,195 +0,0 @@ -# -# tempfile - manipulates temporary files -# -# $Id: tempfile_bugfixed.rb,v 1.2 2005/02/19 20:30:33 thomas Exp $ -# - -require 'delegate' -require 'tmpdir' - -module BugFix #:nodoc:all - - # A class for managing temporary files. This library is written to be - # thread safe. - class Tempfile < DelegateClass(File) - MAX_TRY = 10 - @@cleanlist = [] - - # Creates a temporary file of mode 0600 in the temporary directory - # whose name is basename.pid.n and opens with mode "w+". A Tempfile - # object works just like a File object. - # - # If tmpdir is omitted, the temporary directory is determined by - # Dir::tmpdir provided by 'tmpdir.rb'. - # When $SAFE > 0 and the given tmpdir is tainted, it uses - # /tmp. (Note that ENV values are tainted by default) - def initialize(basename, tmpdir=Dir::tmpdir) - if $SAFE > 0 and tmpdir.tainted? - tmpdir = '/tmp' - end - - lock = nil - n = failure = 0 - - begin - Thread.critical = true - - begin - tmpname = sprintf('%s/%s%d.%d', tmpdir, basename, $$, n) - lock = tmpname + '.lock' - n += 1 - end while @@cleanlist.include?(tmpname) || - ::File.exist?(lock) || ::File.exist?(tmpname) - - Dir.mkdir(lock) - rescue - failure += 1 - retry if failure < MAX_TRY - raise "cannot generate tempfile `%s'" % tmpname - ensure - Thread.critical = false - end - - @data = [tmpname] - @clean_proc = Tempfile.callback(@data) - ObjectSpace.define_finalizer(self, @clean_proc) - - @tmpfile = ::File.open(tmpname, ::File::RDWR|::File::CREAT|::File::EXCL, 0600) - @tmpname = tmpname - @@cleanlist << @tmpname - @data[1] = @tmpfile - @data[2] = @@cleanlist - - super(@tmpfile) - - # Now we have all the File/IO methods defined, you must not - # carelessly put bare puts(), etc. after this. - - Dir.rmdir(lock) - end - - # Opens or reopens the file with mode "r+". - def open - @tmpfile.close if @tmpfile - @tmpfile = ::File.open(@tmpname, 'r+') - @data[1] = @tmpfile - __setobj__(@tmpfile) - end - - def _close # :nodoc: - @tmpfile.close if @tmpfile - @data[1] = @tmpfile = nil - end - protected :_close - - # Closes the file. If the optional flag is true, unlinks the file - # after closing. - # - # If you don't explicitly unlink the temporary file, the removal - # will be delayed until the object is finalized. - def close(unlink_now=false) - if unlink_now - close! - else - _close - end - end - - # Closes and unlinks the file. - def close! - _close - @clean_proc.call - ObjectSpace.undefine_finalizer(self) - end - - # Unlinks the file. On UNIX-like systems, it is often a good idea - # to unlink a temporary file immediately after creating and opening - # it, because it leaves other programs zero chance to access the - # file. - def unlink - # keep this order for thread safeness - ::File.unlink(@tmpname) if ::File.exist?(@tmpname) - @@cleanlist.delete(@tmpname) if @@cleanlist - end - alias :delete :unlink - - if RUBY_VERSION > '1.8.0' - def __setobj__(obj) - @_dc_obj = obj - end - else - def __setobj__(obj) - @obj = obj - end - end - - # Returns the full path name of the temporary file. - def path - @tmpname - end - - # Returns the size of the temporary file. As a side effect, the IO - # buffer is flushed before determining the size. - def size - if @tmpfile - @tmpfile.flush - @tmpfile.stat.size - else - 0 - end - end - alias length size - - class << self - def callback(data) # :nodoc: - pid = $$ - lambda{ - if pid == $$ - path, tmpfile, cleanlist = *data - - print "removing ", path, "..." if $DEBUG - - tmpfile.close if tmpfile - - # keep this order for thread safeness - ::File.unlink(path) if ::File.exist?(path) - cleanlist.delete(path) if cleanlist - - print "done\n" if $DEBUG - end - } - end - - # If no block is given, this is a synonym for new(). - # - # If a block is given, it will be passed tempfile as an argument, - # and the tempfile will automatically be closed when the block - # terminates. In this case, open() returns nil. - def open(*args) - tempfile = new(*args) - - if block_given? - begin - yield(tempfile) - ensure - tempfile.close - end - - nil - else - tempfile - end - end - end - end - -end # module BugFix -if __FILE__ == $0 - # $DEBUG = true - f = Tempfile.new("foo") - f.print("foo\n") - f.close - f.open - p f.gets # => "foo\n" - f.close! -end diff --git a/lib/zip/version.rb b/lib/zip/version.rb new file mode 100644 index 00000000..eb9cfa9b --- /dev/null +++ b/lib/zip/version.rb @@ -0,0 +1,3 @@ +module Zip + VERSION = '2.0.0' +end diff --git a/lib/zip/zip.rb b/lib/zip/zip.rb deleted file mode 100755 index ed83e9e3..00000000 --- a/lib/zip/zip.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'delegate' -require 'singleton' -require 'tempfile' -require 'fileutils' -require 'stringio' -require 'zlib' -require 'zip/dos_time' -require 'zip/ioextras' -require 'rbconfig' - -require 'zip/zip_entry' -require 'zip/zip_extra_field' -require 'zip/zip_entry_set' -require 'zip/zip_central_directory' -require 'zip/zip_file' -require 'zip/zip_input_stream' -require 'zip/zip_output_stream' -require 'zip/decompressor' -require 'zip/compressor' -require 'zip/null_decompressor' -require 'zip/null_compressor' -require 'zip/null_input_stream' -require 'zip/pass_thru_compressor' -require 'zip/pass_thru_decompressor' -require 'zip/inflater' -require 'zip/deflater' -require 'zip/zip_streamable_stream' -require 'zip/zip_streamable_directory' -require 'zip/constants' - -require 'zip/settings' - -if Tempfile.superclass == SimpleDelegator - require 'zip/tempfile_bugfixed' - Tempfile = BugFix::Tempfile -end - -module Zlib #:nodoc:all - if !const_defined?(:MAX_WBITS) - MAX_WBITS = Zlib::Deflate.MAX_WBITS - end -end - -module Zip - class ZipError < StandardError ; end - - class ZipEntryExistsError < ZipError; end - class ZipDestinationFileExistsError < ZipError; end - class ZipCompressionMethodError < ZipError; end - class ZipEntryNameError < ZipError; end - class ZipInternalError < ZipError; end -end - -# Copyright (C) 2002, 2003 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/lib/zip/zip_central_directory.rb b/lib/zip/zip_central_directory.rb deleted file mode 100755 index a933bd92..00000000 --- a/lib/zip/zip_central_directory.rb +++ /dev/null @@ -1,135 +0,0 @@ -module Zip - class ZipCentralDirectory - include Enumerable - - END_OF_CENTRAL_DIRECTORY_SIGNATURE = 0x06054b50 - MAX_END_OF_CENTRAL_DIRECTORY_STRUCTURE_SIZE = 65536 + 18 - STATIC_EOCD_SIZE = 22 - - attr_reader :comment - - # Returns an Enumerable containing the entries. - def entries - @entrySet.entries - end - - def initialize(entries = ZipEntrySet.new, comment = "") #:nodoc: - super() - @entrySet = entries.kind_of?(ZipEntrySet) ? entries : ZipEntrySet.new(entries) - @comment = comment - end - - def write_to_stream(io) #:nodoc: - offset = io.tell - @entrySet.each { |entry| entry.write_c_dir_entry(io) } - write_e_o_c_d(io, offset) - end - - def write_e_o_c_d(io, offset) #:nodoc: - tmp = [ - END_OF_CENTRAL_DIRECTORY_SIGNATURE, - 0 , # @numberOfThisDisk - 0 , # @numberOfDiskWithStartOfCDir - @entrySet? @entrySet.size : 0 , - @entrySet? @entrySet.size : 0 , - cdir_size , - offset , - @comment ? @comment.length : 0 - ] - io << tmp.pack('VvvvvVVv') - io << @comment - end - - private :write_e_o_c_d - - def cdir_size #:nodoc: - # does not include eocd - @entrySet.inject(0) do |value, entry| - entry.cdir_header_size + value - end - end - - private :cdir_size - - def read_e_o_c_d(io) #:nodoc: - buf = get_e_o_c_d(io) - @numberOfThisDisk = ZipEntry.read_zip_short(buf) - @numberOfDiskWithStartOfCDir = ZipEntry.read_zip_short(buf) - @totalNumberOfEntriesInCDirOnThisDisk = ZipEntry.read_zip_short(buf) - @size = ZipEntry.read_zip_short(buf) - @sizeInBytes = ZipEntry.read_zip_long(buf) - @cdirOffset = ZipEntry.read_zip_long(buf) - commentLength = ZipEntry.read_zip_short(buf) - if commentLength <= 0 - @comment = buf.slice!(0, buf.size) - else - @comment = buf.read(commentLength) - end - raise ZipError, "Zip consistency problem while reading eocd structure" unless buf.size == 0 - end - - def read_central_directory_entries(io) #:nodoc: - begin - io.seek(@cdirOffset, IO::SEEK_SET) - rescue Errno::EINVAL - raise ZipError, "Zip consistency problem while reading central directory entry" - end - @entrySet = ZipEntrySet.new - @size.times do - tmp = ZipEntry.read_c_dir_entry(io) - @entrySet << tmp - end - end - - def read_from_stream(io) #:nodoc: - read_e_o_c_d(io) - read_central_directory_entries(io) - end - - def get_e_o_c_d(io) #:nodoc: - begin - io.seek(-MAX_END_OF_CENTRAL_DIRECTORY_STRUCTURE_SIZE, IO::SEEK_END) - rescue Errno::EINVAL - io.seek(0, IO::SEEK_SET) - end - buf = io.read - sigIndex = buf.rindex([END_OF_CENTRAL_DIRECTORY_SIGNATURE].pack('V')) - raise ZipError, "Zip end of central directory signature not found" unless sigIndex - buf = buf.slice!((sigIndex + 4)..(buf.bytesize)) - - def buf.read(count) - slice!(0, count) - end - - buf - end - - # For iterating over the entries. - def each(&proc) - @entrySet.each(&proc) - end - - # Returns the number of entries in the central directory (and - # consequently in the zip archive). - def size - @entrySet.size - end - - def ZipCentralDirectory.read_from_stream(io) #:nodoc: - cdir = new - cdir.read_from_stream(io) - return cdir - rescue ZipError - return nil - end - - def ==(other) #:nodoc: - return false unless other.kind_of?(ZipCentralDirectory) - @entrySet.entries.sort == other.entries.sort && comment == other.comment - end - end -end - - # Copyright (C) 2002, 2003 Thomas Sondergaard - # rubyzip is free software; you can redistribute it and/or - # modify it under the terms of the ruby license. diff --git a/lib/zip/zip_entry.rb b/lib/zip/zip_entry.rb deleted file mode 100755 index a1f8fd10..00000000 --- a/lib/zip/zip_entry.rb +++ /dev/null @@ -1,638 +0,0 @@ -module Zip - class ZipEntry - STORED = 0 - DEFLATED = 8 - - FSTYPE_FAT = 0 - FSTYPE_AMIGA = 1 - FSTYPE_VMS = 2 - FSTYPE_UNIX = 3 - FSTYPE_VM_CMS = 4 - FSTYPE_ATARI = 5 - FSTYPE_HPFS = 6 - FSTYPE_MAC = 7 - FSTYPE_Z_SYSTEM = 8 - FSTYPE_CPM = 9 - FSTYPE_TOPS20 = 10 - FSTYPE_NTFS = 11 - FSTYPE_QDOS = 12 - FSTYPE_ACORN = 13 - FSTYPE_VFAT = 14 - FSTYPE_MVS = 15 - FSTYPE_BEOS = 16 - FSTYPE_TANDEM = 17 - FSTYPE_THEOS = 18 - FSTYPE_MAC_OSX = 19 - 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, - }.freeze - - attr_accessor :comment, :compressed_size, :crc, :extra, :compression_method, - :name, :size, :localHeaderOffset, :zipfile, :fstype, :externalFileAttributes, :gp_flags, :header_signature - - attr_accessor :follow_symlinks - attr_accessor :restore_times, :restore_permissions, :restore_ownership - attr_accessor :unix_uid, :unix_gid, :unix_perms - attr_accessor :dirty - attr_reader :ftype, :filepath # :nodoc: - - def initialize(zipfile = "", name = "", comment = "", extra = "", - compressed_size = 0, crc = 0, - compression_method = ZipEntry::DEFLATED, size = 0, - time = DOSTime.now) - super() - if name.start_with?("/") - raise ZipEntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /" - end - @localHeaderOffset = 0 - @local_header_size = 0 - @internalFileAttributes = 1 - @externalFileAttributes = 0 - @header_signature = CENTRAL_DIRECTORY_ENTRY_SIGNATURE - @versionNeededToExtract = VERSION_NEEDED_TO_EXTRACT - @version = 52 # this library's version - @ftype = nil # unspecified or unknown - @filepath = nil - if Zip::RUNNING_ON_WINDOWS - @fstype = FSTYPE_FAT - else - @fstype = FSTYPE_UNIX - end - @zipfile = zipfile - @comment = comment - @compressed_size = compressed_size - @crc = crc - @extra = extra - @compression_method = compression_method - @name = name - @size = size - @time = time - @gp_flags = 0 - - @follow_symlinks = false - - @restore_times = true - @restore_permissions = false - @restore_ownership = false - -# BUG: need an extra field to support uid/gid's - @unix_uid = nil - @unix_gid = nil - @unix_perms = nil -# @posix_acl = nil -# @ntfs_acl = nil - - if name_is_directory? - @ftype = :directory - else - @ftype = :file - end - - unless ZipExtraField === @extra - @extra = ZipExtraField.new(@extra.to_s) - end - - @dirty = false - end - - def time - if @extra["UniversalTime"] - @extra["UniversalTime"].mtime - else - # Standard time field in central directory has local time - # under archive creator. Then, we can't get timezone. - @time - end - end - alias :mtime :time - - def time=(aTime) - unless @extra.member?("UniversalTime") - @extra.create("UniversalTime") - end - @extra["UniversalTime"].mtime = aTime - @time = aTime - end - - # Returns +true+ if the entry is a directory. - def directory? - raise ZipInternalError, "current filetype is unknown: #{self.inspect}" unless @ftype - @ftype == :directory - end - alias :is_directory :directory? - - # Returns +true+ if the entry is a file. - def file? - raise ZipInternalError, "current filetype is unknown: #{self.inspect}" unless @ftype - @ftype == :file - end - - # Returns +true+ if the entry is a symlink. - def symlink? - raise ZipInternalError, "current filetype is unknown: #{self.inspect}" unless @ftype - @ftype == :symlink - end - - def name_is_directory? #:nodoc:all - (%r{\/$} =~ @name) != nil - end - - def local_entry_offset #:nodoc:all - localHeaderOffset + @local_header_size - end - - def calculate_local_header_size #:nodoc:all - LOCAL_ENTRY_STATIC_HEADER_LENGTH + (@name ? @name.bytesize : 0) + (@extra ? @extra.local_size : 0) - end - - def cdir_header_size #:nodoc:all - CDIR_ENTRY_STATIC_HEADER_LENGTH + (@name ? @name.bytesize : 0) + - (@extra ? @extra.c_dir_size : 0) + (@comment ? @comment.bytesize : 0) - end - - def next_header_offset #:nodoc:all - local_entry_offset + self.compressed_size - end - - # Extracts entry to file destPath (defaults to @name). - def extract(destPath = @name, &onExistsProc) - onExistsProc ||= proc { Zip.options[:on_exists_proc] } - - if directory? - create_directory(destPath, &onExistsProc) - elsif file? - write_file(destPath, &onExistsProc) - elsif symlink? - create_symlink(destPath, &onExistsProc) - else - raise RuntimeError, "unknown file type #{self.inspect}" - end - - self - end - - def to_s - @name - end - - protected - - class << self - def read_zip_short(io) # :nodoc: - io.read(2).unpack('v')[0] - end - - def read_zip_long(io) # :nodoc: - io.read(4).unpack('V')[0] - end - - def read_c_dir_entry(io) #:nodoc:all - entry = new(io.path) - entry.read_c_dir_entry(io) - entry - rescue ZipError - nil - end - - def read_local_entry(io) - entry = new(io.path) - entry.read_local_entry(io) - entry - rescue ZipError - nil - end - - end - - public - - LOCAL_ENTRY_SIGNATURE = 0x04034b50 - LOCAL_ENTRY_STATIC_HEADER_LENGTH = 30 - LOCAL_ENTRY_TRAILING_DESCRIPTOR_LENGTH = 4+4+4 - VERSION_NEEDED_TO_EXTRACT = 20 - - def read_local_entry(io) #:nodoc:all - @localHeaderOffset = io.tell - staticSizedFieldsBuf = io.read(LOCAL_ENTRY_STATIC_HEADER_LENGTH) - unless (staticSizedFieldsBuf.size == LOCAL_ENTRY_STATIC_HEADER_LENGTH) - raise ZipError, "Premature end of file. Not enough data for zip entry local header" - end - - @header_signature , - @version , - @fstype , - @gp_flags , - @compression_method, - lastModTime , - lastModDate , - @crc , - @compressed_size , - @size , - nameLength , - extraLength = staticSizedFieldsBuf.unpack('VCCvvvvVVVvv') - - unless (@header_signature == LOCAL_ENTRY_SIGNATURE) - raise ZipError, "Zip local header magic not found at location '#{localHeaderOffset}'" - end - set_time(lastModDate, lastModTime) - - @name = io.read(nameLength) - extra = io.read(extraLength) - until @name.sub!('\\', '/') == nil do end # some zip files use backslashes instead of slashes as path separators - if (extra && extra.bytesize != extraLength) - raise ZipError, "Truncated local zip entry header" - else - if ZipExtraField === @extra - @extra.merge(extra) - else - @extra = ZipExtraField.new(extra) - end - end - @local_header_size = calculate_local_header_size - end - - - - def write_local_entry(io) #:nodoc:all - @localHeaderOffset = io.tell - - io << - [LOCAL_ENTRY_SIGNATURE , - @versionNeededToExtract , # version needed to extract - @gp_flags , # @gp_flags , - @compression_method , - @time.to_binary_dos_time , # @lastModTime , - @time.to_binary_dos_date , # @lastModDate , - @crc , - @compressed_size , - @size , - @name ? @name.bytesize : 0, - @extra? @extra.local_length : 0 ].pack('VvvvvvVVVvv') - io << @name - io << (@extra ? @extra.to_local_bin : "") - end - - CENTRAL_DIRECTORY_ENTRY_SIGNATURE = 0x02014b50 - CDIR_ENTRY_STATIC_HEADER_LENGTH = 46 - - def read_c_dir_entry(io) #:nodoc:all - staticSizedFieldsBuf = io.read(CDIR_ENTRY_STATIC_HEADER_LENGTH) - unless (staticSizedFieldsBuf.bytesize == CDIR_ENTRY_STATIC_HEADER_LENGTH) - raise ZipError, "Premature end of file. Not enough data for zip cdir entry header" - end - - - @header_signature , - @version , # version of encoding software - @fstype , # filesystem type - @versionNeededToExtract , - @gp_flags , - @compression_method , - lastModTime , - lastModDate , - @crc , - @compressed_size , - @size , - nameLength , - extraLength , - commentLength , - _ , # diskNumberStart - @internalFileAttributes , - @externalFileAttributes , - @localHeaderOffset , - @name , - @extra , - @comment = staticSizedFieldsBuf.unpack('VCCvvvvvVVVvvvvvVV') - - unless (@header_signature == CENTRAL_DIRECTORY_ENTRY_SIGNATURE) - raise ZipError, "Zip local header magic not found at location '#{localHeaderOffset}'" - end - set_time(lastModDate, lastModTime) - - @name = io.read(nameLength) - until @name.sub!('\\', '/') == nil do end # some zip files use backslashes instead of slashes as path separators - if ZipExtraField === @extra - @extra.merge(io.read(extraLength)) - else - @extra = ZipExtraField.new(io.read(extraLength)) - end - @comment = io.read(commentLength) - unless (@comment && @comment.bytesize == commentLength) - raise ZipError, "Truncated cdir zip entry header" - end - - case @fstype - when FSTYPE_UNIX - @unix_perms = (@externalFileAttributes >> 16) & 07777 - - case (@externalFileAttributes >> 28) - when 04 - @ftype = :directory - when 010 - @ftype = :file - when 012 - @ftype = :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 - if name_is_directory? - @ftype = :directory - else - @ftype = :file - end - end - else - if name_is_directory? - @ftype = :directory - else - @ftype = :file - end - end - @local_header_size = calculate_local_header_size - end - - - - def file_stat(path) # :nodoc: - if @follow_symlinks - return ::File::stat(path) - else - return ::File::lstat(path) - end - end - - def get_extra_attributes_from_path(path) # :nodoc: - unless Zip::RUNNING_ON_WINDOWS - stat = file_stat(path) - @unix_uid = stat.uid - @unix_gid = stat.gid - @unix_perms = stat.mode & 07777 - end - end - - def set_extra_attributes_on_path(destPath) # :nodoc: - return unless (file? or directory?) - - case @fstype - when FSTYPE_UNIX - # BUG: does not update timestamps into account - # ignore setuid/setgid bits by default. honor if @restore_ownership - unix_perms_mask = 01777 - unix_perms_mask = 07777 if (@restore_ownership) - FileUtils::chmod(@unix_perms & unix_perms_mask, destPath) if (@restore_permissions && @unix_perms) - FileUtils::chown(@unix_uid, @unix_gid, destPath) if (@restore_ownership && @unix_uid && @unix_gid && Process::egid == 0) - # File::utimes() - end - end - - def write_c_dir_entry(io) #:nodoc:all - case @fstype - when FSTYPE_UNIX - ft = nil - case @ftype - when :file - ft = 010 - @unix_perms ||= 0644 - when :directory - ft = 004 - @unix_perms ||= 0755 - when :symlink - ft = 012 - @unix_perms ||= 0755 - end - - if (!ft.nil?) - @externalFileAttributes = (ft << 12 | (@unix_perms & 07777)) << 16 - end - end - - tmp = [ - @header_signature, - @version , # version of encoding software - @fstype , # filesystem type - @versionNeededToExtract , # @versionNeededToExtract , - @gp_flags , # @gp_flags , - @compression_method , - @time.to_binary_dos_time , # @lastModTime , - @time.to_binary_dos_date , # @lastModDate , - @crc , - @compressed_size , - @size , - @name ? @name.bytesize : 0 , - @extra ? @extra.c_dir_length : 0 , - @comment ? @comment.bytesize : 0 , - 0 , # disk number start - @internalFileAttributes , # file type (binary=0, text=1) - @externalFileAttributes , # native filesystem attributes - @localHeaderOffset , - @name , - @extra , - @comment - ] - - io << tmp.pack('VCCvvvvvVVVvvvvvVV') - - io << @name - io << (@extra ? @extra.to_c_dir_bin : "") - io << @comment - end - - def == (other) - return false unless other.class == self.class - # Compares contents of local entry and exposed fields - (@compression_method == other.compression_method && - @crc == other.crc && - @compressed_size == other.compressed_size && - @size == other.size && - @name == other.name && - @extra == other.extra && - @filepath == other.filepath && - self.time.dos_equals(other.time)) - end - - def <=> (other) - return to_s <=> other.to_s - end - - # Returns an IO like object for the given ZipEntry. - # Warning: may behave weird with symlinks. - def get_input_stream(&aProc) - if @ftype == :directory - return yield(NullInputStream.instance) if block_given? - return NullInputStream.instance - elsif @filepath - case @ftype - when :file - return ::File.open(@filepath, "rb", &aProc) - when :symlink - linkpath = ::File::readlink(@filepath) - stringio = StringIO.new(linkpath) - return yield(stringio) if block_given? - return stringio - else - raise "unknown @ftype #{@ftype}" - end - else - zis = ZipInputStream.new(@zipfile, localHeaderOffset) - zis.get_next_entry - if block_given? - begin - return yield(zis) - ensure - zis.close - end - else - return zis - end - end - end - - def gather_fileinfo_from_srcpath(srcPath) # :nodoc: - stat = file_stat(srcPath) - case stat.ftype - when 'file' - if name_is_directory? - raise ArgumentError, - "entry name '#{newEntry}' indicates directory entry, but "+ - "'#{srcPath}' is not a directory" - end - @ftype = :file - when 'directory' - if ! name_is_directory? - @name += "/" - end - @ftype = :directory - when 'link' - if name_is_directory? - raise ArgumentError, - "entry name '#{newEntry}' indicates directory entry, but "+ - "'#{srcPath}' is not a directory" - end - @ftype = :symlink - else - raise RuntimeError, "unknown file type: #{srcPath.inspect} #{stat.inspect}" - end - - @filepath = srcPath - get_extra_attributes_from_path(@filepath) - end - - def write_to_zip_output_stream(aZipOutputStream) #:nodoc:all - if @ftype == :directory - aZipOutputStream.put_next_entry(self) - elsif @filepath - aZipOutputStream.put_next_entry(self, nil, nil, nil) - get_input_stream { |is| IOExtras.copy_stream(aZipOutputStream, is) } - else - aZipOutputStream.copy_raw_entry(self) - end - end - - def parent_as_string - 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(&aProc) - ::File.open(@zipfile, "rb", &aProc) - end - - private - - def set_time(binaryDosDate, binaryDosTime) - @time = DOSTime.parse_binary_dos_format(binaryDosDate, binaryDosTime) - rescue ArgumentError - puts "Invalid date/time in zip entry" - end - - def write_file(destPath, continueOnExistsProc = proc { Zip.options[:continue_on_exists_proc] }) - if ::File.exists?(destPath) && ! yield(self, destPath) - raise ZipDestinationFileExistsError, - "Destination '#{destPath}' already exists" - end - ::File.open(destPath, "wb") do |os| - get_input_stream do |is| - set_extra_attributes_on_path(destPath) - - buf = '' - while buf = is.sysread(Decompressor::CHUNK_SIZE, buf) - os << buf - end - end - end - end - - def create_directory(destPath) - if ::File.directory?(destPath) - return - elsif ::File.exists?(destPath) - if block_given? && yield(self, destPath) - FileUtils::rm_f destPath - else - raise ZipDestinationFileExistsError, - "Cannot create directory '#{destPath}'. "+ - "A file already exists with that name" - end - end - FileUtils.mkdir_p destPath - set_extra_attributes_on_path(destPath) - end - -# BUG: create_symlink() does not use &onExistsProc - def create_symlink(destPath) - stat = nil - begin - stat = ::File::lstat(destPath) - rescue Errno::ENOENT - end - - io = get_input_stream - linkto = io.read - - if stat - if stat.symlink? - if ::File::readlink(destPath) == linkto - return - else - raise ZipDestinationFileExistsError, - "Cannot create symlink '#{destPath}'. "+ - "A symlink already exists with that name" - end - else - raise ZipDestinationFileExistsError, - "Cannot create symlink '#{destPath}'. "+ - "A file already exists with that name" - end - end - - ::File::symlink(linkto, destPath) - end - end -end - -# Copyright (C) 2002, 2003 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/lib/zip/zip_entry_set.rb b/lib/zip/zip_entry_set.rb deleted file mode 100755 index 0fa66ca7..00000000 --- a/lib/zip/zip_entry_set.rb +++ /dev/null @@ -1,85 +0,0 @@ -module Zip - class ZipEntrySet #:nodoc:all - include Enumerable - - def initialize(anEnumerable = []) - super() - @entrySet = {} - @entryOrder = [] - anEnumerable.each { |o| push(o) } - end - - def include?(entry) - @entrySet.include?(to_key(entry)) - end - - def find_entry(entry) - @entrySet[to_key(entry)] - end - - def <<(entry) - @entryOrder.delete( to_key(entry) ) - @entryOrder << to_key(entry) - @entrySet[to_key(entry)] = entry - end - alias :push :<< - - def size - @entrySet.size - end - - alias :length :size - - def delete(entry) - @entryOrder.delete(to_key(entry)) && @entrySet.delete(to_key(entry)) ? - entry : - nil - end - - def each(&aProc) - @entryOrder.each do |key| - aProc.call @entrySet[key] - end - end - - def entries - @entryOrder.map{|key| @entrySet[key] } - end - - # deep clone - def dup - ZipEntrySet.new(@entryOrder.map { |key| @entrySet[key].dup }) - end - - def ==(other) - return false unless other.kind_of?(ZipEntrySet) - @entrySet == other.entrySet && - @entryOrder == other.entryOrder - end - - def parent(entry) - @entrySet[to_key(entry.parent_as_string)] - end - - def glob(pattern, flags = ::File::FNM_PATHNAME|::File::FNM_DOTMATCH) - entries.map do |entry| - next nil unless ::File.fnmatch(pattern, entry.name.chomp('/'), flags) - yield(entry) if block_given? - entry - end.compact - end - -#TODO attr_accessor :auto_create_directories - protected - attr_accessor :entrySet, :entryOrder - - private - def to_key(entry) - entry.to_s.sub(/\/$/, "") - end - end -end - -# Copyright (C) 2002, 2003 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/lib/zip/zip_extra_field.rb b/lib/zip/zip_extra_field.rb deleted file mode 100755 index 89db1d5d..00000000 --- a/lib/zip/zip_extra_field.rb +++ /dev/null @@ -1,213 +0,0 @@ -module Zip - class ZipExtraField < Hash - ID_MAP = {} - - # Meta class for extra fields - class Generic - def self.register_map - if self.const_defined?(:HEADER_ID) - ID_MAP[self.const_get(:HEADER_ID)] = self - end - end - - def self.name - self.to_s.split("::")[-1] - end - - # 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) - $stderr.puts "Warning: weired extra feild header ID. skip parsing" - 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| - v != other[k] and return false - end - true - end - - def to_local_bin - s = pack_for_local - 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 - end - end - - # Info-ZIP Additional timestamp field - class UniversalTime < Generic - HEADER_ID = "UT" - register_map - - def initialize(binstr = nil) - @ctime = nil - @mtime = nil - @atime = nil - @flag = nil - binstr and merge(binstr) - end - attr_accessor :atime, :ctime, :mtime, :flag - - def merge(binstr) - return if binstr.empty? - size, content = initial_parse(binstr) - size or return - @flag, mtime, atime, ctime = content.unpack("CVVV") - mtime and @mtime ||= DOSTime.at(mtime) - atime and @atime ||= DOSTime.at(atime) - ctime and @ctime ||= DOSTime.at(ctime) - end - - def ==(other) - @mtime == other.mtime && - @atime == other.atime && - @ctime == other.ctime - end - - def pack_for_local - s = [@flag].pack("C") - @flag & 1 != 0 and s << [@mtime.to_i].pack("V") - @flag & 2 != 0 and s << [@atime.to_i].pack("V") - @flag & 4 != 0 and s << [@ctime.to_i].pack("V") - s - end - - def pack_for_c_dir - s = [@flag].pack("C") - @flag & 1 == 1 and s << [@mtime.to_i].pack("V") - s - end - end - - # Info-ZIP Extra for UNIX uid/gid - class IUnix < Generic - HEADER_ID = "Ux" - register_map - - def initialize(binstr = nil) - @uid = 0 - @gid = 0 - binstr and merge(binstr) - end - attr_accessor :uid, :gid - - def merge(binstr) - return if binstr.empty? - size, content = initial_parse(binstr) - # size: 0 for central directory. 4 for local header - return if(!size || size == 0) - uid, gid = content.unpack("vv") - @uid ||= uid - @gid ||= gid - end - - def ==(other) - @uid == other.uid && - @gid == other.gid - end - - def pack_for_local - [@uid, @gid].pack("vv") - end - - def pack_for_c_dir - "" - end - end - - ## start main of ZipExtraField < Hash - def initialize(binstr = nil) - binstr and merge(binstr) - end - - def merge(binstr) - return if binstr.empty? - i = 0 - while i < binstr.bytesize - id = binstr[i,2] - len = binstr[i + 2,2].to_s.unpack("v")[0] - if id && ID_MAP.member?(id) - field_name = ID_MAP[id].name - if self.member?(field_name) - self[field_name].mergea(binstr[i, len + 4]) - else - field_obj = ID_MAP[id].new(binstr[i, len + 4]) - self[field_name] = field_obj - end - elsif id - unless self["Unknown"] - s = "" - class << s - alias_method :to_c_dir_bin, :to_s - alias_method :to_local_bin, :to_s - end - self["Unknown"] = s - end - if !len || len + 4 > binstr[i..-1].bytesize - self["Unknown"] << binstr[i..-1] - break - end - self["Unknown"] << binstr[i, len + 4] - end - i += len + 4 - end - end - - def create(name) - field_class = nil - ID_MAP.each { |id, klass| - if klass.name == name - field_class = klass - break - end - } - if ! field_class - raise ZipError, "Unknown extra field '#{name}'" - end - self[name] = field_class.new() - end - - def to_local_bin - s = "" - each do |k, v| - s << v.to_local_bin - end - s - end - alias :to_s :to_local_bin - - def to_c_dir_bin - s = "" - each do |k, v| - s << v.to_c_dir_bin - end - s - end - - def c_dir_length - to_c_dir_bin.bytesize - end - def local_length - to_local_bin.bytesize - end - alias :c_dir_size :c_dir_length - alias :local_size :local_length - alias :length :local_length - alias :size :local_length - end -end - -# Copyright (C) 2002, 2003 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/lib/zip/zip_file.rb b/lib/zip/zip_file.rb deleted file mode 100755 index 147f40a5..00000000 --- a/lib/zip/zip_file.rb +++ /dev/null @@ -1,402 +0,0 @@ -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 - # 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 - # 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 - # automatically close the archive when the block returns. - # - # 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 - # to it. - # - # require 'zip/zip' - # - # Zip::ZipFile.open("my.zip", Zip::ZipFile::CREATE) { - # |zipfile| - # zipfile.get_output_stream("first.txt") { |f| f.puts "Hello from ZipFile" } - # zipfile.mkdir("a_dir") - # } - # - # The next example reopens my.zip writes the contents of - # first.txt to standard out and deletes the entry from - # the archive. - # - # require 'zip/zip' - # - # Zip::ZipFile.open("my.zip", Zip::ZipFile::CREATE) { - # |zipfile| - # puts zipfile.read("first.txt") - # zipfile.remove("first.txt") - # } - # - # ZipFileSystem offers an alternative API that emulates ruby's - # interface for accessing the filesystem, ie. the File and Dir classes. - - class ZipFile < ZipCentralDirectory - - CREATE = 1 - SPLIT_SIGNATURE = 0x08074b50 - MAX_SEGMENT_SIZE = 3221225472 - MIN_SEGMENT_SIZE = 65536 - DATA_BUFFER_SIZE = 8192 - - attr_reader :name - - # default -> false - attr_accessor :restore_ownership - # default -> false - attr_accessor :restore_permissions - # default -> true - attr_accessor :restore_times - - # Opens a zip archive. Pass true as the second parameter to create - # a new archive if it doesn't exist already. - def initialize(fileName, create = nil, buffer = false) - super() - @name = fileName - @comment = "" - case - when ::File.exists?(fileName) && !buffer - ::File.open(name, "rb") do |f| - read_from_stream(f) - end - when create - @entrySet = ZipEntrySet.new - else - raise ZipError, "File #{fileName} not found" - end - @create = create - @storedEntries = @entrySet.dup - @storedComment = @comment - @restore_ownership = false - @restore_permissions = false - @restore_times = true - 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. - def open(fileName, create = nil) - zf = ZipFile.new(fileName, create) - if block_given? - begin - yield zf - ensure - zf.close - end - else - zf - end - end - - # Same as #open. But outputs data to a buffer instead of a file - def add_buffer - zf = ZipFile.new('', true, true) - yield zf - zf.write_buffer - 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) - zf = ZipFile.new('',true,true) - if io.is_a? IO - zf.read_from_stream(io) - elsif io.is_a? String - require 'stringio' - zf.read_from_stream(StringIO.new(io)) - else - raise "Zip::ZipFile.open_buffer expects an argument of class String or IO. Found: #{io.class}" - end - yield zf - zf.write_buffer - end - - # Iterates over the contents of the ZipFile. This is more efficient - # than using a ZipInputStream since this methods simply iterates - # through the entries in the central directory structure in the archive - # 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) - open(aZipFileName) do |zipFile| - zipFile.each(&block) - 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 ZipError, "File #{zip_file_name} not found" unless ::File.exists?(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 = MIN_SEGMENT_SIZE if MIN_SEGMENT_SIZE > segment_size - segment_size = MAX_SEGMENT_SIZE if MAX_SEGMENT_SIZE < segment_size - return if zip_file_size <= segment_size - - segment_count = (zip_file_size / segment_size).to_i + - (zip_file_size % segment_size == 0 ? 0 : 1) - - # Checking for correct zip structure - open(zip_file_name) {} - - - partial_zip_file_name = zip_file_name.sub(/#{File.basename(zip_file_name)}$/, - partial_zip_file_name + File.extname(zip_file_name)) unless partial_zip_file_name.nil? - - partial_zip_file_name ||= zip_file_name - - szip_file_index = 0 - ::File.open(zip_file_name, 'rb') do |zip_file| - until zip_file.eof? - szip_file_index += 1 - ssegment_size = (zip_file_size - zip_file.pos) >= segment_size ? segment_size : zip_file_size - zip_file.pos - szip_file_name = "#{partial_zip_file_name}.#{'%03d'%(szip_file_index)}" - ::File.open(szip_file_name, 'wb') do |szip_file| - if szip_file_index == 1 - signature_packed = [SPLIT_SIGNATURE].pack('V') - szip_file << signature_packed - ssegment_size = segment_size - signature_packed.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 - end - - ::File.delete(zip_file_name) if delete_zip_file - - szip_file_index - end - - end - - # Returns the zip files comment, if it has one - attr_accessor :comment - - # 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) - end - - # Returns an output 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_output_stream(entry, permissionInt = nil, &aProc) - newEntry = entry.kind_of?(ZipEntry) ? entry : ZipEntry.new(@name, entry.to_s) - if newEntry.directory? - raise ArgumentError, - "cannot open stream to directory entry - '#{newEntry}'" - end - newEntry.unix_perms = permissionInt - zipStreamableEntry = ZipStreamableStream.new(newEntry) - @entrySet << zipStreamableEntry - zipStreamableEntry.get_output_stream(&aProc) - end - - # Returns the name of the zip archive - def to_s - @name - end - - # Returns a string containing the contents of the specified entry - def read(entry) - get_input_stream(entry) { |is| is.read } - end - - # Convenience method for adding the contents of a file to the archive - def add(entry, srcPath, &continueOnExistsProc) - continueOnExistsProc ||= proc { Zip.options[:continue_on_exists_proc] } - check_entry_exists(entry, continueOnExistsProc, "add") - newEntry = entry.kind_of?(ZipEntry) ? entry : ZipEntry.new(@name, entry.to_s) - newEntry.gather_fileinfo_from_srcpath(srcPath) - @entrySet << newEntry - end - - # Removes the specified entry. - def remove(entry) - @entrySet.delete(get_entry(entry)) - end - - # Renames the specified entry. - def rename(entry, newName, &continueOnExistsProc) - foundEntry = get_entry(entry) - check_entry_exists(newName, continueOnExistsProc, "rename") - @entrySet.delete(foundEntry) - foundEntry.name = newName - @entrySet << foundEntry - end - - # Replaces the specified entry with the contents of srcPath (from - # the file system). - def replace(entry, srcPath) - check_file(srcPath) - remove(entry) - add(entry, srcPath) - end - - # Extracts entry to file destPath. - def extract(entry, destPath, &onExistsProc) - onExistsProc ||= proc { Zip.options[:on_exists_proc] } - foundEntry = get_entry(entry) - foundEntry.extract(destPath, &onExistsProc) - end - - # Commits changes that has been made since the previous commit to - # the zip archive. - def commit - return if !commit_required? - on_success_replace(name) { - |tmpFile| - ZipOutputStream.open(tmpFile) { - |zos| - - @entrySet.each { - |e| - e.write_to_zip_output_stream(zos) - e.dirty = false - } - zos.comment = comment - } - true - } - initialize(name) - end - - # Write buffer write changes to buffer and return - def write_buffer - buffer = ZipOutputStream.write_buffer do |zos| - @entrySet.each { |e| e.write_to_zip_output_stream(zos) } - zos.comment = comment - end - return buffer - end - - # Closes the zip file committing any changes that has been made. - def close - commit - end - - # Returns true if any changes has been made to this archive since - # the previous commit - def commit_required? - @entrySet.each do |e| - return true if e.dirty - end - @comment != @storedComment || @entrySet != @storedEntries || @create == ZipFile::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) - @entrySet.find_entry(entry_name) - end - - # Searches for entries given a glob - def glob(*args,&block) - @entrySet.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) - selectedEntry = find_entry(entry) - unless selectedEntry - raise Errno::ENOENT, entry - end - selectedEntry.restore_ownership = @restore_ownership - selectedEntry.restore_permissions = @restore_permissions - selectedEntry.restore_times = @restore_times - selectedEntry - end - - # Creates a directory - def mkdir(entryName, permissionInt = 0755) - if find_entry(entryName) - raise Errno::EEXIST, "File exists - #{entryName}" - end - entryName = entryName.dup.to_s - entryName << '/' unless entryName.end_with?('/') - @entrySet << ZipStreamableDirectory.new(@name, entryName, nil, permissionInt) - end - - private - - def is_directory(newEntry, srcPath) - srcPathIsDirectory = ::File.directory?(srcPath) - if newEntry.is_directory && ! srcPathIsDirectory - raise ArgumentError, - "entry name '#{newEntry}' indicates directory entry, but "+ - "'#{srcPath}' is not a directory" - elsif !newEntry.is_directory && srcPathIsDirectory - newEntry.name += "/" - end - newEntry.is_directory && srcPathIsDirectory - end - - def check_entry_exists(entryName, continueOnExistsProc, procedureName) - continueOnExistsProc ||= proc { Zip.options[:continue_on_exists_proc] } - if @entrySet.include?(entryName) - if continueOnExistsProc.call - remove get_entry(entryName) - else - raise ZipEntryExistsError, - procedureName + " failed. Entry #{entryName} already exists" - end - end - end - - def check_file(path) - unless ::File.readable?(path) - raise Errno::ENOENT, path - end - end - - def on_success_replace(aFilename) - tmpfile = get_tempfile - tmpFilename = tmpfile.path - tmpfile.close - if yield tmpFilename - ::File.rename(tmpFilename, name) - end - end - - def get_tempfile - tempFile = Tempfile.new(::File.basename(name), ::File.dirname(name)) - tempFile.binmode - tempFile - end - - end -end - -# Copyright (C) 2002, 2003 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/lib/zip/zip_input_stream.rb b/lib/zip/zip_input_stream.rb deleted file mode 100755 index acf53621..00000000 --- a/lib/zip/zip_input_stream.rb +++ /dev/null @@ -1,145 +0,0 @@ -module Zip - # ZipInputStream is the basic class for reading zip entries in a - # zip file. It is possible to create a ZipInputStream object directly, - # passing the zip file name to the constructor, but more often than not - # the ZipInputStream will be obtained from a ZipFile (perhaps using the - # ZipFileSystem interface) object for a particular entry in the zip - # archive. - # - # A ZipInputStream inherits IOExtras::AbstractInputStream in order - # to provide an IO-like interface for reading from a single zip - # entry. Beyond methods for mimicking an IO-object it contains - # the method get_next_entry for iterating through the entries of - # an archive. get_next_entry returns a ZipEntry object that describes - # the zip entry the ZipInputStream is currently reading from. - # - # Example that creates a zip archive with ZipOutputStream and reads it - # back again with a ZipInputStream. - # - # require 'zip/zip' - # - # Zip::ZipOutputStream::open("my.zip") { - # |io| - # - # io.put_next_entry("first_entry.txt") - # io.write "Hello world!" - # - # io.put_next_entry("adir/first_entry.txt") - # io.write "Hello again!" - # } - # - # - # Zip::ZipInputStream::open("my.zip") { - # |io| - # - # while (entry = io.get_next_entry) - # puts "Contents of #{entry.name}: '#{io.read}'" - # end - # } - # - # java.util.zip.ZipInputStream is the original inspiration for this - # class. - - class ZipInputStream - include IOExtras::AbstractInputStream - - # Opens the indicated zip file. An exception is thrown - # if the specified offset in the specified filename is - # not a local zip entry header. - def initialize(filename, offset = 0, io = nil) - super() - if (io.nil?) - @archiveIO = ::File.open(filename, "rb") - @archiveIO.seek(offset, IO::SEEK_SET) - else - @archiveIO = io - end - @decompressor = NullDecompressor.instance - @currentEntry = nil - end - - def close - @archiveIO.close - end - - # Same as #initialize but if a block is passed the opened - # stream is passed to the block and closed when the block - # returns. - def ZipInputStream.open(filename) - return new(filename) unless block_given? - - zio = new(filename) - yield zio - ensure - zio.close if zio - end - - def ZipInputStream.open_buffer(io) - return new('',0,io) unless block_given? - zio = new('',0,io) - yield zio - ensure - zio.close if zio - end - - # Returns a ZipEntry object. It is necessary to call this - # method on a newly created ZipInputStream before reading from - # the first entry in the archive. Returns nil when there are - # no more entries. - - def get_next_entry - @archiveIO.seek(@currentEntry.next_header_offset, IO::SEEK_SET) if @currentEntry - open_entry - end - - # Rewinds the stream to the beginning of the current entry - def rewind - return if @currentEntry.nil? - @lineno = 0 - @pos = 0 - @archiveIO.seek(@currentEntry.localHeaderOffset, - IO::SEEK_SET) - open_entry - end - - # Modeled after IO.sysread - def sysread(numberOfBytes = nil, buf = nil) - @decompressor.sysread(numberOfBytes, buf) - end - - def eof - @outputBuffer.empty? && @decompressor.eof - end - alias :eof? :eof - - protected - - def open_entry - @currentEntry = ZipEntry.read_local_entry(@archiveIO) - if @currentEntry.nil? - @decompressor = NullDecompressor.instance - elsif @currentEntry.compression_method == ZipEntry::STORED - @decompressor = PassThruDecompressor.new(@archiveIO, @currentEntry.size) - elsif @currentEntry.compression_method == ZipEntry::DEFLATED - @decompressor = Inflater.new(@archiveIO) - else - raise ZipCompressionMethodError, - "Unsupported compression method #{@currentEntry.compression_method}" - end - flush - return @currentEntry - end - - def produce_input - @decompressor.produce_input - end - - def input_finished? - @decompressor.input_finished? - end - end -end - -# Copyright (C) 2002, 2003 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/lib/zip/zip_output_stream.rb b/lib/zip/zip_output_stream.rb deleted file mode 100755 index c5d4682b..00000000 --- a/lib/zip/zip_output_stream.rb +++ /dev/null @@ -1,173 +0,0 @@ -module Zip - # ZipOutputStream is the basic class for writing zip files. It is - # possible to create a ZipOutputStream object directly, passing - # the zip file name to the constructor, but more often than not - # the ZipOutputStream will be obtained from a ZipFile (perhaps using the - # ZipFileSystem interface) object for a particular entry in the zip - # archive. - # - # A ZipOutputStream inherits IOExtras::AbstractOutputStream in order - # to provide an IO-like interface for writing to a single zip - # entry. Beyond methods for mimicking an IO-object it contains - # the method put_next_entry that closes the current entry - # and creates a new. - # - # Please refer to ZipInputStream for example code. - # - # java.util.zip.ZipOutputStream is the original inspiration for this - # class. - - class ZipOutputStream - include IOExtras::AbstractOutputStream - - attr_accessor :comment - - # Opens the indicated zip file. If a file with that name already - # exists it will be overwritten. - def initialize(fileName, stream=false) - super() - @fileName = fileName - if stream - @outputStream = StringIO.new - else - @outputStream = ::File.new(@fileName, "wb") - end - @entrySet = ZipEntrySet.new - @compressor = NullCompressor.instance - @closed = false - @currentEntry = nil - @comment = 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. - def ZipOutputStream.open(fileName) - return new(fileName) unless block_given? - zos = new(fileName) - yield zos - ensure - zos.close if zos - end - - # Same as #open but writes to a filestream instead - def ZipOutputStream.write_buffer - zos = new('', true) - yield zos - return zos.close_buffer - end - - # 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 - @outputStream.close - @closed = true - end - - # Closes the stream and writes the central directory to the zip file - def close_buffer - return @outputStream if @closed - finalize_current_entry - update_local_headers - write_central_directory - @closed = true - @outputStream - end - - # Closes the current entry and opens a new for writing. - # +entry+ can be a ZipEntry object or a string. - def put_next_entry(entryname, comment = nil, extra = nil, compression_method = ZipEntry::DEFLATED, level = Zlib::DEFAULT_COMPRESSION) - raise ZipError, "zip stream is closed" if @closed - if entryname.kind_of?(ZipEntry) - new_entry = entryname - else - new_entry = ZipEntry.new(@fileName, entryname.to_s) - end - new_entry.comment = comment if !comment.nil? - if (!extra.nil?) - new_entry.extra = ZipExtraField === extra ? extra : ZipExtraField.new(extra.to_s) - end - new_entry.compression_method = compression_method if !compression_method.nil? - init_next_entry(new_entry, level) - @currentEntry = new_entry - end - - def copy_raw_entry(entry) - entry = entry.dup - raise ZipError, "zip stream is closed" if @closed - raise ZipError, "entry is not a ZipEntry" if !entry.kind_of?(ZipEntry) - finalize_current_entry - @entrySet << entry - src_pos = entry.local_entry_offset - entry.write_local_entry(@outputStream) - @compressor = NullCompressor.instance - entry.get_raw_input_stream do |is| - is.seek(src_pos, IO::SEEK_SET) - IOExtras.copy_stream_n(@outputStream, is, entry.compressed_size) - end - @compressor = NullCompressor.instance - @currentEntry = nil - end - - private - - def finalize_current_entry - return unless @currentEntry - finish - @currentEntry.compressed_size = @outputStream.tell - @currentEntry.localHeaderOffset - @currentEntry.calculate_local_header_size - @currentEntry.size = @compressor.size - @currentEntry.crc = @compressor.crc - @currentEntry = nil - @compressor = NullCompressor.instance - end - - def init_next_entry(entry, level = Zlib::DEFAULT_COMPRESSION) - finalize_current_entry - @entrySet << entry - entry.write_local_entry(@outputStream) - @compressor = get_compressor(entry, level) - end - - def get_compressor(entry, level) - case entry.compression_method - when ZipEntry::DEFLATED then Deflater.new(@outputStream, level) - when ZipEntry::STORED then PassThruCompressor.new(@outputStream) - else raise ZipCompressionMethodError, - "Invalid compression method: '#{entry.compression_method}'" - end - end - - def update_local_headers - pos = @outputStream.pos - @entrySet.each do |entry| - @outputStream.pos = entry.localHeaderOffset - entry.write_local_entry(@outputStream) - end - @outputStream.pos = pos - end - - def write_central_directory - cdir = ZipCentralDirectory.new(@entrySet, @comment) - cdir.write_to_stream(@outputStream) - end - - protected - - def finish - @compressor.finish - end - - public - # Modeled after IO.<< - def << (data) - @compressor << data - end - end -end - -# Copyright (C) 2002, 2003 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/rubyzip.gemspec b/rubyzip.gemspec index 451b7196..f8c59a18 100644 --- a/rubyzip.gemspec +++ b/rubyzip.gemspec @@ -1,12 +1,31 @@ -spec = Gem::Specification.new do |s| - s.name = 'rubyzip' - s.version = "0.9.9" - s.author = "Alan Harper" - s.email = "alan@aussiegeek.net" - s.homepage = "http://github.com/aussiegeek/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 NEWS TODO Rakefile } - s.require_path = 'lib' - s.required_ruby_version = '>= 1.8.7' +#-*- encoding: utf-8 -*- + +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require '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 = { + '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 '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 'rubocop', '~> 0.49.1' end diff --git a/samples/example.rb b/samples/example.rb index 6592100e..224d4f1c 100755 --- a/samples/example.rb +++ b/samples/example.rb @@ -1,89 +1,79 @@ #!/usr/bin/env ruby -$: << "../lib" -system("zip example.zip example.rb gtkRubyzip.rb") +$: << '../lib' +system('zip example.zip example.rb gtk_ruby_zip.rb') -require 'zip/zip' +require 'zip' ####### Using ZipInputStream alone: ####### -Zip::ZipInputStream.open("example.zip") { - |zis| +Zip::InputStream.open('example.zip') do |zis| entry = zis.get_next_entry print "First line of '#{entry.name} (#{entry.size} bytes): " puts "'#{zis.gets.chomp}'" entry = zis.get_next_entry print "First line of '#{entry.name} (#{entry.size} bytes): " puts "'#{zis.gets.chomp}'" -} - +end ####### Using ZipFile to read the directory of a zip file: ####### -zf = Zip::ZipFile.new("example.zip") -zf.each_with_index { - |entry, index| - +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}" # 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. -} - +end ####### Using ZipOutputStream to write a zip file: ####### -Zip::ZipOutputStream.open("exampleout.zip") { - |zos| - zos.put_next_entry("the first little entry") - zos.puts "Hello hello hello hello hello hello hello hello hello" +Zip::OutputStream.open('exampleout.zip') do |zos| + zos.put_next_entry('the first little entry') + zos.puts 'Hello hello hello hello hello hello hello hello hello' - zos.put_next_entry("the second little entry") - zos.puts "Hello again" + zos.put_next_entry('the second little entry') + zos.puts 'Hello again' # Use rubyzip or your zip client of choice to verify # the contents of exampleout.zip -} +end ####### Using ZipFile to change a zip file: ####### -Zip::ZipFile.open("exampleout.zip") { - |zf| - zf.add("thisFile.rb", "example.rb") - zf.rename("thisFile.rb", "ILikeThisName.rb") - zf.add("Again", "example.rb") -} +Zip::File.open('exampleout.zip') do |zip_file| + zip_file.add('thisFile.rb', 'example.rb') + zip_file.rename('thisFile.rb', 'ILikeThisName.rb') + zip_file.add('Again', 'example.rb') +end # Lets check -Zip::ZipFile.open("exampleout.zip") { - |zf| - puts "Changed zip file contains: #{zf.entries.join(', ')}" - zf.remove("Again") - puts "Without 'Again': #{zf.entries.join(', ')}" -} +Zip::File.open('exampleout.zip') do |zip_file| + puts "Changed zip file contains: #{zip_file.entries.join(', ')}" + zip_file.remove('Again') + puts "Without 'Again': #{zip_file.entries.join(', ')}" +end ####### Using ZipFile to split a zip file: ####### # Creating large zip file for splitting -Zip::ZipOutputStream.open("large_zip_file.zip") do |zos| - puts "Creating zip file..." +Zip::OutputStream.open('large_zip_file.zip') do |zos| + puts 'Creating zip file...' 10.times do |i| zos.put_next_entry("large_entry_#{i}.txt") - zos.puts "Hello" * 104857600 + zos.puts 'Hello' * 104_857_600 end end # Splitting created large zip file -part_zips_count = Zip::ZipFile.split("large_zip_file.zip", 2097152, false) +part_zips_count = Zip::File.split('large_zip_file.zip', 2_097_152, false) puts "Zip file splitted in #{part_zips_count} parts" # Track splitting an archive -Zip::ZipFile.split("large_zip_file.zip", 1048576, 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}%" +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}%" end - # For other examples, look at zip.rb and ziptest.rb # Copyright (C) 2002 Thomas Sondergaard diff --git a/samples/example_filesystem.rb b/samples/example_filesystem.rb index 0cacbd2a..f253a5e5 100755 --- a/samples/example_filesystem.rb +++ b/samples/example_filesystem.rb @@ -1,30 +1,28 @@ #!/usr/bin/env ruby -$: << "../lib" +$: << '../lib' -require 'zip/zipfilesystem' +require 'zip/filesystem' -EXAMPLE_ZIP = "filesystem.zip" +EXAMPLE_ZIP = 'filesystem.zip' -File.delete(EXAMPLE_ZIP) if File.exists?(EXAMPLE_ZIP) +File.delete(EXAMPLE_ZIP) if File.exist?(EXAMPLE_ZIP) -Zip::ZipFile.open(EXAMPLE_ZIP, Zip::ZipFile::CREATE) { - |zf| - zf.file.open("file1.txt", "w") { |os| os.write "first file1.txt" } - zf.dir.mkdir("dir1") - zf.dir.chdir("dir1") - zf.file.open("file1.txt", "w") { |os| os.write "second file1.txt" } - puts zf.file.read("file1.txt") - puts zf.file.read("../file1.txt") - zf.dir.chdir("..") - zf.file.open("file2.txt", "w") { |os| os.write "first file2.txt" } +Zip::File.open(EXAMPLE_ZIP, Zip::File::CREATE) do |zf| + zf.file.open('file1.txt', 'w') { |os| os.write 'first file1.txt' } + zf.dir.mkdir('dir1') + zf.dir.chdir('dir1') + zf.file.open('file1.txt', 'w') { |os| os.write 'second file1.txt' } + puts zf.file.read('file1.txt') + puts zf.file.read('../file1.txt') + zf.dir.chdir('..') + zf.file.open('file2.txt', 'w') { |os| os.write 'first file2.txt' } puts "Entries: #{zf.entries.join(', ')}" -} +end -Zip::ZipFile.open(EXAMPLE_ZIP) { - |zf| +Zip::File.open(EXAMPLE_ZIP) do |zf| puts "Entries from reloaded zip: #{zf.entries.join(', ')}" -} +end # For other examples, look at zip.rb and ziptest.rb diff --git a/samples/example_recursive.rb b/samples/example_recursive.rb index b1588e00..56a5cc7c 100644 --- a/samples/example_recursive.rb +++ b/samples/example_recursive.rb @@ -1,4 +1,4 @@ -require 'zip/zip' +require 'zip' # This is a simple example which uses rubyzip to # recursively generate a zip file from the contents of @@ -6,44 +6,49 @@ # included in the archive, rather just its contents. # # Usage: -# directoryToZip = "/tmp/input" -# outputFile = "/tmp/out.zip" -# zf = ZipFileGenerator.new(directoryToZip, outputFile) +# directory_to_zip = "/tmp/input" +# output_file = "/tmp/out.zip" +# zf = ZipFileGenerator.new(directory_to_zip, output_file) # zf.write() class ZipFileGenerator - # Initialize with the directory to zip and the location of the output archive. - def initialize(inputDir, outputFile) - @inputDir = inputDir - @outputFile = outputFile + def initialize(input_dir, output_file) + @input_dir = input_dir + @output_file = output_file end # Zip the input directory. - def write() - entries = Dir.entries(@inputDir); entries.delete("."); entries.delete("..") - io = Zip::ZipFile.open(@outputFile, Zip::ZipFile::CREATE); + def write + entries = Dir.entries(@input_dir) - %w[. ..] - writeEntries(entries, "", io) - io.close(); + ::Zip::File.open(@output_file, ::Zip::File::CREATE) do |zipfile| + write_entries entries, '', zipfile + end end - # A helper method to make the recursion work. private - def writeEntries(entries, path, io) - - entries.each { |e| - zipFilePath = path == "" ? e : File.join(path, e) - diskFilePath = File.join(@inputDir, zipFilePath) - puts "Deflating " + diskFilePath - if File.directory?(diskFilePath) - io.mkdir(zipFilePath) - subdir =Dir.entries(diskFilePath); subdir.delete("."); subdir.delete("..") - writeEntries(subdir, zipFilePath, io) + + # A helper method to make the recursion work. + def write_entries(entries, path, zipfile) + entries.each do |e| + zipfile_path = path == '' ? e : File.join(path, e) + disk_file_path = File.join(@input_dir, zipfile_path) + + if File.directory? disk_file_path + recursively_deflate_directory(disk_file_path, zipfile, zipfile_path) else - io.get_output_stream(zipFilePath) { |f| f.puts(File.open(diskFilePath, "rb").read())} + put_into_archive(disk_file_path, zipfile, zipfile_path) end - } + end + end + + def recursively_deflate_directory(disk_file_path, zipfile, zipfile_path) + zipfile.mkdir zipfile_path + subdir = Dir.entries(disk_file_path) - %w[. ..] + write_entries subdir, zipfile_path, zipfile end - -end + def put_into_archive(disk_file_path, zipfile, zipfile_path) + zipfile.add(zipfile_path, disk_file_path) + end +end diff --git a/samples/gtkRubyzip.rb b/samples/gtk_ruby_zip.rb similarity index 66% rename from samples/gtkRubyzip.rb rename to samples/gtk_ruby_zip.rb index 5d91829d..62f005a5 100755 --- a/samples/gtkRubyzip.rb +++ b/samples/gtk_ruby_zip.rb @@ -1,17 +1,17 @@ #!/usr/bin/env ruby -$: << "../lib" +$: << '../lib' $VERBOSE = true require 'gtk' -require 'zip/zip' +require 'zip' class MainApp < Gtk::Window def initialize super() set_usize(400, 256) - set_title("rubyzip") + set_title('rubyzip') signal_connect(Gtk::Window::SIGNAL_DESTROY) { Gtk.main_quit } box = Gtk::VBox.new(false, 0) @@ -19,26 +19,25 @@ def initialize @zipfile = nil @buttonPanel = ButtonPanel.new - @buttonPanel.openButton.signal_connect(Gtk::Button::SIGNAL_CLICKED) { + @buttonPanel.openButton.signal_connect(Gtk::Button::SIGNAL_CLICKED) do show_file_selector - } - @buttonPanel.extractButton.signal_connect(Gtk::Button::SIGNAL_CLICKED) { - puts "Not implemented!" - } + end + @buttonPanel.extractButton.signal_connect(Gtk::Button::SIGNAL_CLICKED) do + puts 'Not implemented!' + end box.pack_start(@buttonPanel, false, false, 0) - + sw = Gtk::ScrolledWindow.new sw.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC) box.pack_start(sw, true, true, 0) - @clist = Gtk::CList.new(["Name", "Size", "Compression"]) + @clist = Gtk::CList.new(%w[Name Size Compression]) @clist.set_selection_mode(Gtk::SELECTION_BROWSE) @clist.set_column_width(0, 120) @clist.set_column_width(1, 120) - @clist.signal_connect(Gtk::CList::SIGNAL_SELECT_ROW) { - |w, row, column, event| + @clist.signal_connect(Gtk::CList::SIGNAL_SELECT_ROW) do |_w, row, _column, _event| @selected_row = row - } + end sw.add(@clist) end @@ -48,38 +47,37 @@ def initialize super set_layout(Gtk::BUTTONBOX_START) set_spacing(0) - @openButton = Gtk::Button.new("Open archive") - @extractButton = Gtk::Button.new("Extract entry") + @openButton = Gtk::Button.new('Open archive') + @extractButton = Gtk::Button.new('Extract entry') pack_start(@openButton) pack_start(@extractButton) end end def show_file_selector - @fileSelector = Gtk::FileSelection.new("Open zip file") + @fileSelector = Gtk::FileSelection.new('Open zip file') @fileSelector.show - @fileSelector.ok_button.signal_connect(Gtk::Button::SIGNAL_CLICKED) { + @fileSelector.ok_button.signal_connect(Gtk::Button::SIGNAL_CLICKED) do open_zip(@fileSelector.filename) @fileSelector.destroy - } - @fileSelector.cancel_button.signal_connect(Gtk::Button::SIGNAL_CLICKED) { + end + @fileSelector.cancel_button.signal_connect(Gtk::Button::SIGNAL_CLICKED) do @fileSelector.destroy - } + end end def open_zip(filename) - @zipfile = Zip::ZipFile.open(filename) + @zipfile = Zip::File.open(filename) @clist.clear - @zipfile.each { - |entry| - @clist.append([ entry.name, - entry.size.to_s, - (100.0*entry.compressedSize/entry.size).to_s+"%" ]) - } + @zipfile.each do |entry| + @clist.append([entry.name, + entry.size.to_s, + (100.0 * entry.compressedSize / entry.size).to_s + '%']) + end end end -mainApp = MainApp.new() +mainApp = MainApp.new mainApp.show_all diff --git a/samples/qtzip.rb b/samples/qtzip.rb index 3d76bd18..1d450a78 100755 --- a/samples/qtzip.rb +++ b/samples/qtzip.rb @@ -1,22 +1,18 @@ #!/usr/bin/env ruby -$VERBOSE=true +$VERBOSE = true -$: << "../lib" +$: << '../lib' require 'Qt' system('rbuic -o zipdialogui.rb zipdialogui.ui') require 'zipdialogui.rb' -require 'zip/zip' - - +require 'zip' a = Qt::Application.new(ARGV) class ZipDialog < ZipDialogUI - - - def initialize() + def initialize super() connect(child('add_button'), SIGNAL('clicked()'), self, SLOT('add_files()')) @@ -25,23 +21,21 @@ def initialize() end def zipfile(&proc) - Zip::ZipFile.open(@zip_filename, &proc) + Zip::File.open(@zip_filename, &proc) end def each(&proc) - Zip::ZipFile.foreach(@zip_filename, &proc) + Zip::File.foreach(@zip_filename, &proc) end - - def refresh() - lv = child("entry_list_view") + + def refresh + lv = child('entry_list_view') lv.clear - each { - |e| + each do |e| lv.insert_item(Qt::ListViewItem.new(lv, e.name, e.size.to_s)) - } + end end - def load(zipfile) @zip_filename = zipfile refresh @@ -49,13 +43,11 @@ def load(zipfile) def add_files l = Qt::FileDialog.getOpenFileNames(nil, nil, self) - zipfile { - |zf| - l.each { - |path| + zipfile do |zf| + l.each do |path| zf.add(File.basename(path), path) - } - } + end + end refresh end @@ -63,7 +55,7 @@ def extract_files selected_items = [] unselected_items = [] lv_item = entry_list_view.first_child - while (lv_item) + while lv_item if entry_list_view.is_selected(lv_item) selected_items << lv_item.text(0) else @@ -73,22 +65,21 @@ def extract_files end puts "selected_items.size = #{selected_items.size}" puts "unselected_items.size = #{unselected_items.size}" - items = selected_items.size > 0 ? selected_items : unselected_items + items = !selected_items.empty? ? selected_items : unselected_items puts "items.size = #{items.size}" d = Qt::FileDialog.get_existing_directory(nil, self) - if (!d) - puts "No directory chosen" + if !d + puts 'No directory chosen' else zipfile { |zf| items.each { |e| zf.extract(e, File.join(d, e)) } } end - end slots 'add_files()', 'extract_files()' end -if !ARGV[0] +unless ARGV[0] puts "usage: #{$0} zipname" exit end @@ -97,5 +88,5 @@ def extract_files zd.load(ARGV[0]) a.mainWidget = zd -zd.show() -a.exec() +zd.show +a.exec diff --git a/samples/write_simple.rb b/samples/write_simple.rb index 5a1f26b1..be2a9704 100755 --- a/samples/write_simple.rb +++ b/samples/write_simple.rb @@ -1,13 +1,12 @@ -#!/usr/bin/env ruby - -$: << "../lib" - -require 'zip/zip' - -include Zip - -ZipOutputStream.open('simple.zip') { - |zos| - ze = zos.put_next_entry 'entry.txt' - zos.puts "Hello world" -} +#!/usr/bin/env ruby + +$: << '../lib' + +require 'zip' + +include Zip + +OutputStream.open('simple.zip') do |zos| + zos.put_next_entry 'entry.txt' + zos.puts 'Hello world' +end diff --git a/samples/zipfind.rb b/samples/zipfind.rb index 54ad936e..400e0a69 100755 --- a/samples/zipfind.rb +++ b/samples/zipfind.rb @@ -2,72 +2,64 @@ $VERBOSE = true -$: << "../lib" +$: << '../lib' -require 'zip/zip' +require 'zip' require 'find' module Zip module ZipFind def self.find(path, zipFilePattern = /\.zip$/i) - Find.find(path) { - |fileName| - yield(fileName) - if zipFilePattern.match(fileName) && File.file?(fileName) - begin - Zip::ZipFile.foreach(fileName) { - |zipEntry| - yield(fileName + File::SEPARATOR + zipEntry.to_s) - } - rescue Errno::EACCES => ex - puts ex - end - end - } + 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) + end + rescue Errno::EACCES => ex + puts ex + end + end end def self.find_file(path, fileNamePattern, zipFilePattern = /\.zip$/i) - self.find(path, zipFilePattern) { - |fileName| - yield(fileName) if fileNamePattern.match(fileName) - } + find(path, zipFilePattern) do |fileName| + yield(fileName) if fileNamePattern.match(fileName) + end end - end end -if __FILE__ == $0 +if $0 == __FILE__ module ZipFindConsoleRunner - - PATH_ARG_INDEX = 0; - FILENAME_PATTERN_ARG_INDEX = 1; - ZIPFILE_PATTERN_ARG_INDEX = 2; - + PATH_ARG_INDEX = 0 + FILENAME_PATTERN_ARG_INDEX = 1 + ZIPFILE_PATTERN_ARG_INDEX = 2 + 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]) { - |fileName| - report_entry_found fileName - } + Zip::ZipFind.find_file(args[PATH_ARG_INDEX], + args[FILENAME_PATTERN_ARG_INDEX], + args[ZIPFILE_PATTERN_ARG_INDEX]) do |fileName| + report_entry_found fileName + end end - + def self.check_args(args) - if (args.size != 3) - usage - exit + if args.size != 3 + usage + exit end end def self.usage puts "Usage: #{$0} PATH ZIPFILENAME_PATTERN FILNAME_PATTERN" end - + def self.report_entry_found(fileName) puts fileName end - end ZipFindConsoleRunner.run(ARGV) diff --git a/test/.cvsignore b/test/.cvsignore deleted file mode 100644 index 0bd75a2a..00000000 --- a/test/.cvsignore +++ /dev/null @@ -1,11 +0,0 @@ -4entry_copy.zip -cdirtest.bin -centralEntryHeader.bin -deflatertest.bin -dummy.txt -emptyOutDir -localEntryHeader.bin -okToDeleteMoved.txt -output.zip -test_putOnClosedStream.zip -zipWithDirs_copy.zip diff --git a/test/alltests.rb b/test/alltests.rb deleted file mode 100755 index e5af30a6..00000000 --- a/test/alltests.rb +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -require 'ioextrastest' -require 'ziptest' -require 'zipfilesystemtest' \ No newline at end of file diff --git a/test/basic_zip_file_test.rb b/test/basic_zip_file_test.rb new file mode 100644 index 00000000..9e490b4a --- /dev/null +++ b/test/basic_zip_file_test.rb @@ -0,0 +1,60 @@ +require 'test_helper' + +class BasicZipFileTest < MiniTest::Test + include AssertEntry + + def setup + @zip_file = ::Zip::File.new(TestZipFile::TEST_ZIP2.zip_name) + @testEntryNameIndex = 0 + end + + def test_entries + assert_equal(TestZipFile::TEST_ZIP2.entry_names.sort, + @zip_file.entries.entries.sort.map { |e| e.name }) + 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) + 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) + end + + def test_get_input_stream + count = 0 + visited = {} + @zip_file.each do |entry| + 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) + 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) + end + end +end diff --git a/test/case_sensitivity_test.rb b/test/case_sensitivity_test.rb new file mode 100644 index 00000000..4aab1667 --- /dev/null +++ b/test/case_sensitivity_test.rb @@ -0,0 +1,69 @@ +require 'test_helper' + +class ZipCaseSensitivityTest < MiniTest::Test + include CommonZipFileFixture + + SRC_FILES = [['test/data/file1.txt', 'testfile.rb'], + ['test/data/file2.txt', 'testFILE.rb']] + + def teardown + ::Zip.case_insensitive_match = false + end + + # Ensure that everything functions normally when +case_insensitive_match = false+ + 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) + + 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) + SRC_FILES.each_with_index { |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 + + # Ensure that names are treated case insensitively when adding files and +case_insensitive_match = false+ + 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) + + assert_raises Zip::EntryExistsError do + SRC_FILES.each { |fn, en| zf.add(en, fn) } + end + end + + # Ensure that names are treated case insensitively when reading files and +case_insensitive_match = true+ + 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) + + SRC_FILES.each { |fn, en| zf.add(en, fn) } + zf.close + + ::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) + AssertEntry.assert_contents(SRC_FILES.last.first, + zfRead.get_input_stream(SRC_FILES.last.last) { |zis| zis.read }) + end + + 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}") + assert_entry_contents(zf, entryName, filename) if File.exist?(filename) + end +end diff --git a/test/central_directory_entry_test.rb b/test/central_directory_entry_test.rb new file mode 100644 index 00000000..fa0d8065 --- /dev/null +++ b/test/central_directory_entry_test.rb @@ -0,0 +1,69 @@ +require 'test_helper' + +class ZipCentralDirectoryEntryTest < MiniTest::Test + def test_read_from_stream + File.open('test/data/testDirectory.bin', 'rb') do |file| + entry = ::Zip::Entry.read_c_dir_entry(file) + + assert_equal('longAscii.txt', entry.name) + assert_equal(::Zip::Entry::DEFLATED, entry.compression_method) + assert_equal(106_490, entry.size) + assert_equal(3784, entry.compressed_size) + assert_equal(0xfcd1799c, entry.crc) + assert_equal('', entry.comment) + + entry = ::Zip::Entry.read_c_dir_entry(file) + assert_equal('empty.txt', entry.name) + assert_equal(::Zip::Entry::STORED, entry.compression_method) + assert_equal(0, entry.size) + assert_equal(0, entry.compressed_size) + assert_equal(0x0, entry.crc) + assert_equal('', entry.comment) + + entry = ::Zip::Entry.read_c_dir_entry(file) + assert_equal('short.txt', entry.name) + assert_equal(::Zip::Entry::STORED, entry.compression_method) + assert_equal(6, entry.size) + assert_equal(6, entry.compressed_size) + assert_equal(0xbb76fe69, entry.crc) + assert_equal('', entry.comment) + + entry = ::Zip::Entry.read_c_dir_entry(file) + assert_equal('longBinary.bin', entry.name) + assert_equal(::Zip::Entry::DEFLATED, entry.compression_method) + assert_equal(1_000_024, entry.size) + assert_equal(70_847, entry.compressed_size) + assert_equal(0x10da7d59, entry.crc) + assert_equal('', entry.comment) + + entry = ::Zip::Entry.read_c_dir_entry(file) + assert_nil(entry) + # Fields that are not check by this test: + # version made by 2 bytes + # version needed to extract 2 bytes + # general purpose bit flag 2 bytes + # last mod file time 2 bytes + # last mod file date 2 bytes + # compressed size 4 bytes + # uncompressed size 4 bytes + # disk number start 2 bytes + # internal file attributes 2 bytes + # external file attributes 4 bytes + # relative offset of local header 4 bytes + + # file name (variable size) + # extra field (variable size) + # file comment (variable size) + 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) + fail 'ZipError expected' + rescue ::Zip::Error + end +end diff --git a/test/central_directory_test.rb b/test/central_directory_test.rb new file mode 100644 index 00000000..26be6424 --- /dev/null +++ b/test/central_directory_test.rb @@ -0,0 +1,100 @@ +require 'test_helper' + +class ZipCentralDirectoryTest < MiniTest::Test + def teardown + ::Zip.reset! + end + + def test_read_from_stream + ::File.open(TestZipFile::TEST_ZIP2.zip_name, 'rb') do |zipFile| + cdir = ::Zip::CentralDirectory.read_from_stream(zipFile) + + 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 + 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| + cdir = ::Zip::CentralDirectory.new + cdir.read_from_stream(zipFile) + end + fail 'ZipError expected!' + 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) + fail 'ZipError expected' + rescue ::Zip::Error + 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')] + 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) } + + assert_equal(cdir.entries.sort, cdirReadback.entries.sort) + end + + 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)] + [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) } + + assert_equal(cdir.entries.sort, cdirReadback.entries.sort) + assert_equal(::Zip::VERSION_NEEDED_TO_EXTRACT_ZIP64, cdirReadback.instance_variable_get(:@version_needed_for_extract)) + 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?') + assert_equal(cdir1, cdir1) + assert_equal(cdir1, cdir2) + + assert(cdir1 != cdir3) + assert(cdir2 != cdir3) + assert(cdir2 != cdir3) + assert(cdir3 != cdir4) + + assert(cdir3 != 'hello') + end +end diff --git a/test/crypto/null_encryption_test.rb b/test/crypto/null_encryption_test.rb new file mode 100644 index 00000000..ca039962 --- /dev/null +++ b/test/crypto/null_encryption_test.rb @@ -0,0 +1,57 @@ +require 'test_helper' + +class NullEncrypterTest < MiniTest::Test + def setup + @encrypter = ::Zip::NullEncrypter.new + end + + def test_header_bytesize + assert_equal 0, @encrypter.header_bytesize + end + + def test_gp_flags + assert_equal 0, @encrypter.gp_flags + end + + def test_header + assert_empty @encrypter.header(nil) + end + + def test_encrypt + assert_nil @encrypter.encrypt(nil) + + ['', 'a' * 10, 0xffffffff].each do |data| + assert_equal data, @encrypter.encrypt(data) + end + end + + def test_reset! + assert_respond_to @encrypter, :reset! + end +end + +class NullDecrypterTest < MiniTest::Test + def setup + @decrypter = ::Zip::NullDecrypter.new + end + + def test_header_bytesize + assert_equal 0, @decrypter.header_bytesize + end + + def test_gp_flags + assert_equal 0, @decrypter.gp_flags + end + + def test_decrypt + assert_nil @decrypter.decrypt(nil) + + ['', 'a' * 10, 0xffffffff].each do |data| + assert_equal data, @decrypter.decrypt(data) + end + end + + def test_reset! + assert_respond_to @decrypter, :reset! + end +end diff --git a/test/crypto/traditional_encryption_test.rb b/test/crypto/traditional_encryption_test.rb new file mode 100644 index 00000000..51f6cbb4 --- /dev/null +++ b/test/crypto/traditional_encryption_test.rb @@ -0,0 +1,80 @@ +require 'test_helper' + +class TraditionalEncrypterTest < MiniTest::Test + def setup + @mtime = ::Zip::DOSTime.new(2014, 12, 17, 15, 56, 24) + @encrypter = ::Zip::TraditionalEncrypter.new('password') + end + + def test_header_bytesize + assert_equal 12, @encrypter.header_bytesize + end + + def test_gp_flags + assert_equal 9, @encrypter.gp_flags + end + + def test_header + @encrypter.reset! + exepected = [239, 57, 234, 154, 246, 80, 83, 221, 74, 200, 121, 91].pack('C*') + Random.stub(:rand, 1) do + assert_equal exepected, @encrypter.header(@mtime) + end + end + + def test_encrypt + @encrypter.reset! + Random.stub(:rand, 1) { @encrypter.header(@mtime) } + assert_raises(NoMethodError) { @encrypter.encrypt(nil) } + assert_raises(NoMethodError) { @encrypter.encrypt(1) } + assert_equal '', @encrypter.encrypt('') + assert_equal [100, 218, 7, 114, 226, 82, 62, 93, 224, 62].pack('C*'), @encrypter.encrypt('a' * 10) + end + + def test_reset! + @encrypter.reset! + Random.stub(:rand, 1) { @encrypter.header(@mtime) } + [100, 218, 7, 114, 226, 82, 62, 93, 224, 62].map(&:chr).each do |c| + assert_equal c, @encrypter.encrypt('a') + end + assert_equal 56.chr, @encrypter.encrypt('a') + @encrypter.reset! + Random.stub(:rand, 1) { @encrypter.header(@mtime) } + [100, 218, 7, 114, 226, 82, 62, 93, 224, 62].map(&:chr).each do |c| + assert_equal c, @encrypter.encrypt('a') + end + end +end + +class TraditionalDecrypterTest < MiniTest::Test + def setup + @decrypter = ::Zip::TraditionalDecrypter.new('password') + end + + def test_header_bytesize + assert_equal 12, @decrypter.header_bytesize + end + + def test_gp_flags + assert_equal 9, @decrypter.gp_flags + end + + def test_decrypt + @decrypter.reset!([239, 57, 234, 154, 246, 80, 83, 221, 74, 200, 121, 91].pack('C*')) + [100, 218, 7, 114, 226, 82, 62, 93, 224, 62].map(&:chr).each do |c| + assert_equal 'a', @decrypter.decrypt(c) + end + end + + def test_reset! + @decrypter.reset!([239, 57, 234, 154, 246, 80, 83, 221, 74, 200, 121, 91].pack('C*')) + [100, 218, 7, 114, 226, 82, 62, 93, 224, 62].map(&:chr).each do |c| + assert_equal 'a', @decrypter.decrypt(c) + end + assert_equal 91.chr, @decrypter.decrypt(2.chr) + @decrypter.reset!([239, 57, 234, 154, 246, 80, 83, 221, 74, 200, 121, 91].pack('C*')) + [100, 218, 7, 114, 226, 82, 62, 93, 224, 62].map(&:chr).each do |c| + assert_equal 'a', @decrypter.decrypt(c) + end + end +end diff --git a/test/data/.cvsignore b/test/data/.cvsignore deleted file mode 100644 index 86d4c2dd..00000000 --- a/test/data/.cvsignore +++ /dev/null @@ -1 +0,0 @@ -generated diff --git a/test/data/WarnInvalidDate.zip b/test/data/WarnInvalidDate.zip new file mode 100644 index 00000000..01fca9bb Binary files /dev/null and b/test/data/WarnInvalidDate.zip differ diff --git a/test/data/gpbit3stored.zip b/test/data/gpbit3stored.zip new file mode 100644 index 00000000..3c73eeb3 Binary files /dev/null and b/test/data/gpbit3stored.zip differ diff --git a/test/data/mimetype b/test/data/mimetype new file mode 100644 index 00000000..57ef03f2 --- /dev/null +++ b/test/data/mimetype @@ -0,0 +1 @@ +application/epub+zip \ No newline at end of file diff --git a/test/data/notzippedruby.rb b/test/data/notzippedruby.rb index 036d25e9..79f9cbb9 100755 --- a/test/data/notzippedruby.rb +++ b/test/data/notzippedruby.rb @@ -1,7 +1,7 @@ #!/usr/bin/env ruby class NotZippedRuby - def returnTrue + def return_true true end end diff --git a/test/data/ntfs.zip b/test/data/ntfs.zip new file mode 100644 index 00000000..3f944c9b Binary files /dev/null and b/test/data/ntfs.zip differ diff --git a/test/data/oddExtraField.zip b/test/data/oddExtraField.zip new file mode 100644 index 00000000..eeb1504b Binary files /dev/null and b/test/data/oddExtraField.zip differ diff --git a/test/data/path_traversal/Makefile b/test/data/path_traversal/Makefile new file mode 100644 index 00000000..9ff4d816 --- /dev/null +++ b/test/data/path_traversal/Makefile @@ -0,0 +1,10 @@ +# Based on 'relative2' in https://github.com/jwilk/path-traversal-samples, +# but create the local `tmp` folder before adding the symlink. Otherwise +# we may bail out before we get to trying to create the file. +all: relative1.zip +relative1.zip: + rm -f $(@) + mkdir -p -m 755 tmp/tmp + umask 022 && echo moo > moo + cd tmp && zip -X ../$(@) tmp tmp/../../moo + rm -rf tmp moo diff --git a/test/data/path_traversal/jwilk/README.md b/test/data/path_traversal/jwilk/README.md new file mode 100644 index 00000000..2ecceb23 --- /dev/null +++ b/test/data/path_traversal/jwilk/README.md @@ -0,0 +1,5 @@ +# Path Traversal Samples + +Copied from https://github.com/jwilk/path-traversal-samples on 2018-08-26. + +License: MIT diff --git a/test/data/path_traversal/jwilk/absolute1.zip b/test/data/path_traversal/jwilk/absolute1.zip new file mode 100644 index 00000000..27c615d9 Binary files /dev/null and b/test/data/path_traversal/jwilk/absolute1.zip differ diff --git a/test/data/path_traversal/jwilk/absolute2.zip b/test/data/path_traversal/jwilk/absolute2.zip new file mode 100644 index 00000000..c82c14ea Binary files /dev/null and b/test/data/path_traversal/jwilk/absolute2.zip differ diff --git a/test/data/path_traversal/jwilk/dirsymlink.zip b/test/data/path_traversal/jwilk/dirsymlink.zip new file mode 100644 index 00000000..978b5d8a Binary files /dev/null and b/test/data/path_traversal/jwilk/dirsymlink.zip differ diff --git a/test/data/path_traversal/jwilk/dirsymlink2a.zip b/test/data/path_traversal/jwilk/dirsymlink2a.zip new file mode 100644 index 00000000..443deede Binary files /dev/null and b/test/data/path_traversal/jwilk/dirsymlink2a.zip differ diff --git a/test/data/path_traversal/jwilk/dirsymlink2b.zip b/test/data/path_traversal/jwilk/dirsymlink2b.zip new file mode 100644 index 00000000..5a5a12b4 Binary files /dev/null and b/test/data/path_traversal/jwilk/dirsymlink2b.zip differ diff --git a/test/data/path_traversal/jwilk/relative0.zip b/test/data/path_traversal/jwilk/relative0.zip new file mode 100644 index 00000000..d27a0d08 Binary files /dev/null and b/test/data/path_traversal/jwilk/relative0.zip differ diff --git a/test/data/path_traversal/jwilk/relative2.zip b/test/data/path_traversal/jwilk/relative2.zip new file mode 100644 index 00000000..8957028d Binary files /dev/null and b/test/data/path_traversal/jwilk/relative2.zip differ diff --git a/test/data/path_traversal/jwilk/symlink.zip b/test/data/path_traversal/jwilk/symlink.zip new file mode 100644 index 00000000..edaa7526 Binary files /dev/null and b/test/data/path_traversal/jwilk/symlink.zip differ diff --git a/test/data/path_traversal/relative1.zip b/test/data/path_traversal/relative1.zip new file mode 100644 index 00000000..bfcb9def Binary files /dev/null and b/test/data/path_traversal/relative1.zip differ diff --git a/test/data/path_traversal/tilde.zip b/test/data/path_traversal/tilde.zip new file mode 100644 index 00000000..0442ab93 Binary files /dev/null and b/test/data/path_traversal/tilde.zip differ diff --git a/test/data/path_traversal/tuzovakaoff/README.md b/test/data/path_traversal/tuzovakaoff/README.md new file mode 100644 index 00000000..f599810e --- /dev/null +++ b/test/data/path_traversal/tuzovakaoff/README.md @@ -0,0 +1,3 @@ +# Path Traversal Samples + +Copied from https://github.com/tuzovakaoff/zip_path_traversal on 2018-08-25. diff --git a/test/data/path_traversal/tuzovakaoff/absolutepath.zip b/test/data/path_traversal/tuzovakaoff/absolutepath.zip new file mode 100644 index 00000000..59fceed7 Binary files /dev/null and b/test/data/path_traversal/tuzovakaoff/absolutepath.zip differ diff --git a/test/data/path_traversal/tuzovakaoff/symlink.zip b/test/data/path_traversal/tuzovakaoff/symlink.zip new file mode 100644 index 00000000..e74ee19a Binary files /dev/null and b/test/data/path_traversal/tuzovakaoff/symlink.zip differ diff --git a/test/data/rubycode.zip b/test/data/rubycode.zip index 8a68560e..06134bbc 100644 Binary files a/test/data/rubycode.zip and b/test/data/rubycode.zip differ diff --git a/test/data/test.xls b/test/data/test.xls new file mode 100644 index 00000000..5e4ec61f Binary files /dev/null and b/test/data/test.xls differ diff --git a/test/data/zip64-sample.zip b/test/data/zip64-sample.zip new file mode 100644 index 00000000..81fda7d0 Binary files /dev/null and b/test/data/zip64-sample.zip differ diff --git a/test/data/zipWithEncryption.zip b/test/data/zipWithEncryption.zip new file mode 100644 index 00000000..e102b875 Binary files /dev/null and b/test/data/zipWithEncryption.zip differ diff --git a/test/deflater_test.rb b/test/deflater_test.rb new file mode 100644 index 00000000..e4f552ef --- /dev/null +++ b/test/deflater_test.rb @@ -0,0 +1,65 @@ +require 'test_helper' + +class DeflaterTest < MiniTest::Test + include CrcTest + + DEFLATER_TEST_FILE = 'test/data/generated/deflatertest.bin' + BEST_COMP_FILE = 'test/data/generated/compressiontest_best_compression.bin' + DEFAULT_COMP_FILE = 'test/data/generated/compressiontest_default_compression.bin' + NO_COMP_FILE = 'test/data/generated/compressiontest_no_compression.bin' + + 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) + end + + def test_default_compression + txt = load_file('test/data/file2.txt') + + Zip.default_compression = ::Zlib::BEST_COMPRESSION + deflate(txt, BEST_COMP_FILE) + Zip.default_compression = ::Zlib::DEFAULT_COMPRESSION + deflate(txt, DEFAULT_COMP_FILE) + Zip.default_compression = ::Zlib::NO_COMPRESSION + deflate(txt, NO_COMP_FILE) + + best = File.size(BEST_COMP_FILE) + default = File.size(DEFAULT_COMP_FILE) + no = File.size(NO_COMP_FILE) + + assert(best < default) + assert(best < no) + assert(default < no) + end + + private + + def load_file(fileName) + txt = nil + File.open(fileName, 'rb') { |f| txt = f.read } + end + + def deflate(data, fileName) + File.open(fileName, 'wb') do |file| + deflater = ::Zip::Deflater.new(file) + deflater << data + deflater.finish + assert_equal(deflater.size, data.size) + file << 'trailing data for zlib with -MAX_WBITS' + end + end + + def inflate(fileName) + txt = nil + File.open(fileName, 'rb') do |file| + inflater = ::Zip::Inflater.new(file) + txt = inflater.sysread + end + end + + def test_crc + run_crc_test(::Zip::Deflater) + end +end diff --git a/test/encryption_test.rb b/test/encryption_test.rb new file mode 100644 index 00000000..46770a17 --- /dev/null +++ b/test/encryption_test.rb @@ -0,0 +1,42 @@ +require 'test_helper' + +class EncryptionTest < MiniTest::Test + ENCRYPT_ZIP_TEST_FILE = 'test/data/zipWithEncryption.zip' + 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 + end + + def test_encrypt + test_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 + end.string + end + end + + @output.unpack('C*').each_with_index do |c, i| + assert_equal test_file[i].ord, c + end + end + + def test_decrypt + Zip::InputStream.open(ENCRYPT_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 + end + end +end diff --git a/test/entry_set_test.rb b/test/entry_set_test.rb new file mode 100644 index 00000000..6501ab86 --- /dev/null +++ b/test/entry_set_test.rb @@ -0,0 +1,163 @@ +require 'test_helper' + +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') + ] + + def setup + @zipEntrySet = ::Zip::EntrySet.new(ZIP_ENTRIES) + end + + def teardown + ::Zip.reset! + end + + def test_include + assert(@zipEntrySet.include?(ZIP_ENTRIES.first)) + assert(!@zipEntrySet.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) + end + + def test_add + zes = ::Zip::EntrySet.new + entry1 = ::Zip::Entry.new('zf.zip', 'name1') + entry2 = ::Zip::Entry.new('zf.zip', 'name2') + zes << entry1 + assert(zes.include?(entry1)) + zes.push(entry2) + assert(zes.include?(entry2)) + 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.first, entry) + + entry = @zipEntrySet.delete(ZIP_ENTRIES.first) + assert_equal(ZIP_ENTRIES.size - 1, @zipEntrySet.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| + assert(ZIP_ENTRIES.include?(entry)) + count += 1 + end + assert_equal(ZIP_ENTRIES.size, count) + end + + def test_entries + assert_equal(ZIP_ENTRIES, @zipEntrySet.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.case_insensitive_match = false + zipEntrySet = ::Zip::EntrySet.new(entries) + assert_equal(entries[0], zipEntrySet.find_entry('MiXeDcAsEnAmE')) + assert_nil(zipEntrySet.find_entry('mixedcasename')) + end + + def test_entries_with_sort + ::Zip.sort_entries = true + assert_equal(ZIP_ENTRIES.sort, @zipEntrySet.entries) + ::Zip.sort_entries = false + assert_equal(ZIP_ENTRIES, @zipEntrySet.entries) + end + + def test_entries_sorted_in_each + ::Zip.sort_entries = true + arr = [] + @zipEntrySet.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)) + + @zipEntrySet.delete(newEntry) + assert_equal(ZIP_ENTRIES.size, @zipEntrySet.size) + end + + def test_dup + copy = @zipEntrySet.dup + assert_equal(@zipEntrySet, copy) + + # demonstrate that this is a deep copy + copy.entries[0].name = 'a totally different name' + assert(@zipEntrySet != copy) + end + + def test_parent + entries = [ + ::Zip::Entry.new('zf.zip', 'a/'), + ::Zip::Entry.new('zf.zip', 'a/b/'), + ::Zip::Entry.new('zf.zip', 'a/b/c/') + ] + entrySet = ::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])) + end + + def test_glob + res = @zipEntrySet.glob('name[2-4]') + assert_equal(3, res.size) + assert_equal(ZIP_ENTRIES[1, 3].sort, res.sort) + end + + def test_glob2 + entries = [ + ::Zip::Entry.new('zf.zip', 'a/'), + ::Zip::Entry.new('zf.zip', 'a/b/b1'), + ::Zip::Entry.new('zf.zip', 'a/b/c/'), + ::Zip::Entry.new('zf.zip', 'a/b/c/c1') + ] + entrySet = ::Zip::EntrySet.new(entries) + + assert_equal(entries[0, 1], entrySet.glob('*')) + # assert_equal(entries[FIXME], entrySet.glob("**")) + # res = entrySet.glob('a*') + # assert_equal(entries.size, res.size) + # assert_equal(entrySet.map { |e| e.name }, res.map { |e| e.name }) + end + + def test_glob3 + entries = [ + ::Zip::Entry.new('zf.zip', 'a/a'), + ::Zip::Entry.new('zf.zip', 'a/b'), + ::Zip::Entry.new('zf.zip', 'a/c') + ] + entrySet = ::Zip::EntrySet.new(entries) + + assert_equal(entries[0, 2].sort, entrySet.glob('a/{a,b}').sort) + end +end diff --git a/test/entry_test.rb b/test/entry_test.rb new file mode 100644 index 00000000..b49783d3 --- /dev/null +++ b/test/entry_test.rb @@ -0,0 +1,154 @@ +require 'test_helper' + +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_SIZE, + TEST_TIME) + + assert_equal(TEST_COMMENT, entry.comment) + assert_equal(TEST_COMPRESSED_SIZE, entry.compressed_size) + assert_equal(TEST_CRC, entry.crc) + assert_instance_of(::Zip::ExtraField, entry.extra) + 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) + end + + def test_is_directory_and_is_file + assert(::Zip::Entry.new(TEST_ZIPFILE, 'hello').file?) + assert(!::Zip::Entry.new(TEST_ZIPFILE, 'hello').directory?) + + assert(::Zip::Entry.new(TEST_ZIPFILE, 'dir/hello').file?) + assert(!::Zip::Entry.new(TEST_ZIPFILE, 'dir/hello').directory?) + + assert(::Zip::Entry.new(TEST_ZIPFILE, 'hello/').directory?) + assert(!::Zip::Entry.new(TEST_ZIPFILE, 'hello/').file?) + + assert(::Zip::Entry.new(TEST_ZIPFILE, 'dir/hello/').directory?) + assert(!::Zip::Entry.new(TEST_ZIPFILE, 'dir/hello/').file?) + end + + def test_equality + entry1 = ::Zip::Entry.new('file.zip', 'name', 'isNotCompared', + 'something extra', 123, 1234, + ::Zip::Entry::DEFLATED, 10_000) + entry2 = ::Zip::Entry.new('file.zip', 'name', 'isNotComparedXXX', + 'something extra', 123, 1234, + ::Zip::Entry::DEFLATED, 10_000) + entry3 = ::Zip::Entry.new('file.zip', 'name2', 'isNotComparedXXX', + 'something extra', 123, 1234, + ::Zip::Entry::DEFLATED, 10_000) + entry4 = ::Zip::Entry.new('file.zip', 'name2', 'isNotComparedXXX', + 'something extraXX', 123, 1234, + ::Zip::Entry::DEFLATED, 10_000) + entry5 = ::Zip::Entry.new('file.zip', 'name2', 'isNotComparedXXX', + 'something extraXX', 12, 1234, + ::Zip::Entry::DEFLATED, 10_000) + entry6 = ::Zip::Entry.new('file.zip', 'name2', 'isNotComparedXXX', + 'something extraXX', 12, 123, + ::Zip::Entry::DEFLATED, 10_000) + entry7 = ::Zip::Entry.new('file.zip', 'name2', 'isNotComparedXXX', + 'something extraXX', 12, 123, + ::Zip::Entry::STORED, 10_000) + entry8 = ::Zip::Entry.new('file.zip', 'name2', 'isNotComparedXXX', + 'something extraXX', 12, 123, + ::Zip::Entry::STORED, 100_000) + + assert_equal(entry1, entry1) + assert_equal(entry1, entry2) + + assert(entry2 != entry3) + assert(entry3 != entry4) + assert(entry4 != entry5) + assert(entry5 != entry6) + assert(entry6 != entry7) + assert(entry7 != entry8) + + assert(entry7 != 'hello') + assert(entry7 != 12) + end + + def test_compare + assert_equal(0, (::Zip::Entry.new('zf.zip', 'a') <=> ::Zip::Entry.new('zf.zip', 'a'))) + assert_equal(1, (::Zip::Entry.new('zf.zip', 'b') <=> ::Zip::Entry.new('zf.zip', 'a'))) + assert_equal(-1, (::Zip::Entry.new('zf.zip', 'a') <=> ::Zip::Entry.new('zf.zip', 'b'))) + + entries = [ + ::Zip::Entry.new('zf.zip', '5'), + ::Zip::Entry.new('zf.zip', '1'), + ::Zip::Entry.new('zf.zip', '3'), + ::Zip::Entry.new('zf.zip', '4'), + ::Zip::Entry.new('zf.zip', '0'), + ::Zip::Entry.new('zf.zip', '2') + ] + + entries.sort! + assert_equal('0', entries[0].to_s) + assert_equal('1', entries[1].to_s) + assert_equal('2', entries[2].to_s) + assert_equal('3', entries[3].to_s) + assert_equal('4', entries[4].to_s) + assert_equal('5', entries[5].to_s) + end + + def test_parent_as_string + entry1 = ::Zip::Entry.new('zf.zip', 'aa') + entry2 = ::Zip::Entry.new('zf.zip', 'aa/') + entry3 = ::Zip::Entry.new('zf.zip', 'aa/bb') + entry4 = ::Zip::Entry.new('zf.zip', 'aa/bb/') + entry5 = ::Zip::Entry.new('zf.zip', 'aa/bb/cc') + entry6 = ::Zip::Entry.new('zf.zip', 'aa/bb/cc/') + + assert_nil(entry1.parent_as_string) + assert_nil(entry2.parent_as_string) + assert_equal('aa/', entry3.parent_as_string) + assert_equal('aa/', entry4.parent_as_string) + assert_equal('aa/bb/', entry5.parent_as_string) + assert_equal('aa/bb/', entry6.parent_as_string) + end + + def test_entry_name_cannot_start_with_slash + assert_raises(::Zip::EntryNameError) { ::Zip::Entry.new('zf.zip', '/hej/der') } + 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', '**', '**')] + + 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 + '', # @comment + '', # @extra + 0, # @compressed_size + 0, # @crc + Zip::Entry::STORED) # @comppressed_method + + zipfile.add(mimetype_entry, 'test/data/mimetype') + + files.each do |file| + zipfile.add(file.sub('test/data/globTest/', ''), file) + end + zipfile.close + + f = File.open('/tmp/no_compress.zip', 'rb') + first_100_bytes = f.read(100) + f.close + + assert_match(/mimetypeapplication\/epub\+zip/, first_100_bytes) + end +end diff --git a/test/errors_test.rb b/test/errors_test.rb new file mode 100644 index 00000000..2c6adb2f --- /dev/null +++ b/test/errors_test.rb @@ -0,0 +1,35 @@ +# encoding: utf-8 + +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 diff --git a/test/extEntry b/test/extEntry deleted file mode 100644 index 05259899..00000000 --- a/test/extEntry +++ /dev/null @@ -1,4512 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -require 'rubyunit' -require 'zip' - -include Zip - -Dir.chdir "test" - -class AbstractInputStreamTest < RUNIT::TestCase - # AbstractInputStream subclass that provides a read method - - TEST_LINES = [ "Hello world#{$/}", - "this is the second line#{$/}", - "this is the last line"] - TEST_STRING = TEST_LINES.join - class TestAbstractInputStream - include AbstractInputStream - def initialize(aString) - @contents = aString - @readPointer = 0 - end - - def read(charsToRead) - retVal=@contents[@readPointer, charsToRead] - @readPointer+=charsToRead - return retVal - end - - def produceInput - read(100) - end - - def inputFinished? - @contents[@readPointer] == nil - end - end - - def setup - @io = TestAbstractInputStream.new(TEST_STRING) - end - - def test_gets - assert_equals(TEST_LINES[0], @io.gets) - assert_equals(TEST_LINES[1], @io.gets) - assert_equals(TEST_LINES[2], @io.gets) - assert_equals(nil, @io.gets) - end - - def test_getsMultiCharSeperator - assert_equals("Hell", @io.gets("ll")) - assert_equals("o world#{$/}this is the second l", @io.gets("d l")) - end - - def test_each_line - lineNumber=0 - @io.each_line { - |line| - assert_equals(TEST_LINES[lineNumber], line) - lineNumber+=1 - } - end - - def test_readlines - assert_equals(TEST_LINES, @io.readlines) - end - - def test_readline - test_gets - begin - @io.readline - fail "EOFError expected" - rescue EOFError - end - end -end - -class ZipEntryTest < RUNIT::TestCase - TEST_ZIPFILE = "someZipFile.zip" - TEST_COMMENT = "a comment" - TEST_COMPRESSED_SIZE = 1234 - TEST_CRC = 325324 - TEST_EXTRA = "Some data here" - TEST_COMPRESSIONMETHOD = ZipEntry::DEFLATED - TEST_NAME = "entry name" - TEST_SIZE = 8432 - TEST_ISDIRECTORY = false - - def test_constructorAndGetters - entry = ZipEntry.new(TEST_ZIPFILE, - TEST_NAME, - TEST_COMMENT, - TEST_EXTRA, - TEST_COMPRESSED_SIZE, - TEST_CRC, - TEST_COMPRESSIONMETHOD, - TEST_SIZE) - - assert_equals(TEST_COMMENT, entry.comment) - assert_equals(TEST_COMPRESSED_SIZE, entry.compressedSize) - assert_equals(TEST_CRC, entry.crc) - assert_equals(TEST_EXTRA, entry.extra) - assert_equals(TEST_COMPRESSIONMETHOD, entry.compressionMethod) - assert_equals(TEST_NAME, entry.name) - assert_equals(TEST_SIZE, entry.size) - assert_equals(TEST_ISDIRECTORY, entry.isDirectory) - end - - def test_equality - entry1 = ZipEntry.new("file.zip", "name", "isNotCompared", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry2 = ZipEntry.new("file.zip", "name", "isNotComparedXXX", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry3 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry4 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry5 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 1234, - ZipEntry::DEFLATED, 10000) - entry6 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::DEFLATED, 10000) - entry7 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::STORED, 10000) - entry8 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::STORED, 100000) - - assert_equals(entry1, entry1) - assert_equals(entry1, entry2) - - assert(entry2 != entry3) - assert(entry3 != entry4) - assert(entry4 != entry5) - assert(entry5 != entry6) - assert(entry6 != entry7) - assert(entry7 != entry8) - - assert(entry7 != "hello") - assert(entry7 != 12) - end -end - -module IOizeString - attr_reader :tell - - def read(count = nil) - @tell ||= 0 - count = size unless count - retVal = slice(@tell, count) - @tell += count - return retVal - end - - def seek(index, offset) - @tell ||= 0 - case offset - when IO::SEEK_END - newPos = size + index - when IO::SEEK_SET - newPos = index - when IO::SEEK_CUR - newPos = @tell + index - else - raise "Error in test method IOizeString::seek" - end - if (newPos < 0 || newPos >= size) - raise Errno::EINVAL - else - @tell=newPos - end - end - - def reset - @tell = 0 - end -end - -class ZipLocalEntryTest < RUNIT::TestCase - def test_readLocalEntryHeaderOfFirstTestZipEntry - File.open(TestZipFile::TEST_ZIP3.zipName) { - |file| - entry = ZipEntry.readLocalEntry(file) - - assert_equal("", entry.comment) - # Differs from windows and unix because of CR LF - # assert_equal(480, entry.compressedSize) - # assert_equal(0x2a27930f, entry.crc) - # extra field is 21 bytes long - # probably contains some unix attrutes or something - # disabled: assert_equal(nil, entry.extra) - assert_equal(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equal(TestZipFile::TEST_ZIP3.entryNames[0], entry.name) - assert_equal(File.size(TestZipFile::TEST_ZIP3.entryNames[0]), entry.size) - assert(! entry.isDirectory) - } - end - - def test_readLocalEntryFromNonZipFile - File.open("ziptest.rb") { - |file| - assert_equals(nil, ZipEntry.readLocalEntry(file)) - } - end - - def test_readLocalEntryFromTruncatedZipFile - zipFragment="" - File.open(TestZipFile::TEST_ZIP2.zipName) { |f| zipFragment = f.read(12) } # local header is at least 30 bytes - zipFragment.extend(IOizeString).reset - entry = ZipEntry.new - entry.readLocalEntry(zipFragment) - fail "ZipError expected" - rescue ZipError - end - - def test_writeEntry - entry = ZipEntry.new("file.zip", "entryName", "my little comment", - "thisIsSomeExtraInformation", 100, 987654, - ZipEntry::DEFLATED, 400) - writeToFile("localEntryHeader.bin", "centralEntryHeader.bin", entry) - entryReadLocal, entryReadCentral = readFromFile("localEntryHeader.bin", "centralEntryHeader.bin") - compareLocalEntryHeaders(entry, entryReadLocal) - compareCDirEntryHeaders(entry, entryReadCentral) - end - - private - def compareLocalEntryHeaders(entry1, entry2) - assert_equals(entry1.compressedSize , entry2.compressedSize) - assert_equals(entry1.crc , entry2.crc) - assert_equals(entry1.extra , entry2.extra) - assert_equals(entry1.compressionMethod, entry2.compressionMethod) - assert_equals(entry1.name , entry2.name) - assert_equals(entry1.size , entry2.size) - assert_equals(entry1.localHeaderOffset, entry2.localHeaderOffset) - end - - def compareCDirEntryHeaders(entry1, entry2) - compareLocalEntryHeaders(entry1, entry2) - assert_equals(entry1.comment, entry2.comment) - end - - def writeToFile(localFileName, centralFileName, entry) - File.open(localFileName, "wb") { |f| entry.writeLocalEntry(f) } - File.open(centralFileName, "wb") { |f| entry.writeCDirEntry(f) } - end - - def readFromFile(localFileName, centralFileName) - localEntry = nil - cdirEntry = nil - File.open(localFileName, "rb") { |f| localEntry = ZipEntry.readLocalEntry(f) } - File.open(centralFileName, "rb") { |f| cdirEntry = ZipEntry.readCDirEntry(f) } - return [localEntry, cdirEntry] - end -end - - -module DecompressorTests - # expects @refText and @decompressor - - def test_readEverything - assert_equals(@refText, @decompressor.read) - end - - def test_readInChunks - chunkSize = 5 - while (decompressedChunk = @decompressor.read(chunkSize)) - assert_equals(@refText.slice!(0, chunkSize), decompressedChunk) - end - assert_equals(0, @refText.size) - end -end - -class InflaterTest < RUNIT::TestCase - include DecompressorTests - - def setup - @file = File.new("file1.txt.deflatedData", "rb") - @refText="" - File.open("file1.txt") { |f| @refText = f.read } - @decompressor = Inflater.new(@file) - end - - def teardown - @file.close - end -end - - -class PassThruDecompressorTest < RUNIT::TestCase - include DecompressorTests - TEST_FILE="file1.txt" - def setup - @file = File.new(TEST_FILE) - @refText="" - File.open(TEST_FILE) { |f| @refText = f.read } - @decompressor = PassThruDecompressor.new(@file, File.size(TEST_FILE)) - end - - def teardown - @file.close - end -end - - -module AssertEntry - def assertNextEntry(filename, zis) - assertEntry(filename, zis, zis.getNextEntry.name) - end - - def assertEntry(filename, zis, entryName) - assert_equals(filename, entryName) - assertEntryContentsForStream(filename, zis, entryName) - end - - def assertEntryContentsForStream(filename, zis, entryName) - File.open(filename, "rb") { - |file| - expected = file.read - actual = zis.read - if (expected != actual) - if (expected.length > 400 || actual.length > 400) - zipEntryFilename=entryName+".zipEntry" - File.open(zipEntryFilename, "wb") { |file| file << actual } - fail("File '#{filename}' is different from '#{zipEntryFilename}'") - else - assert_equals(expected, actual) - end - end - } - end - - def AssertEntry.assertContents(filename, aString) - fileContents = "" - File.open(filename, "rb") { |f| fileContents = f.read } - if (fileContents != aString) - if (expected.length > 400 || actual.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_equals(expected, actual) - end - end - end - - def assertStreamContents(zis, testZipFile) - assert(zis != nil) - testZipFile.entryNames.each { - |entryName| - assertNextEntry(entryName, zis) - } - assert_equals(nil, zis.getNextEntry) - end - - def assertTestZipContents(testZipFile) - ZipInputStream.open(testZipFile.zipName) { - |zis| - assertStreamContents(zis, testZipFile) - } - end - - def assertEntryContents(zipFile, entryName, filename = entryName.to_s) - zis = zipFile.getInputStream(entryName) - assertEntryContentsForStream(filename, zis, entryName) - ensure - zis.close if zis - end -end - - - -class ZipInputStreamTest < RUNIT::TestCase - include AssertEntry - - def test_new - zis = ZipInputStream.new(TestZipFile::TEST_ZIP2.zipName) - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - zis.close - end - - def test_openWithBlock - ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) { - |zis| - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - } - end - - def test_openWithoutBlock - zis = ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - end - - def test_incompleteReads - ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) { - |zis| - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[0], entry.name) - assert zis.gets.length > 0 - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[1], entry.name) - assert_equals(0, entry.size) - assert_equals(nil, zis.gets) - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[2], entry.name) - assert zis.gets.length > 0 - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[3], entry.name) - assert zis.gets.length > 0 - } - end - -end - -class TestFiles - RANDOM_ASCII_FILE1 = "randomAscii1.txt" - RANDOM_ASCII_FILE2 = "randomAscii2.txt" - RANDOM_ASCII_FILE3 = "randomAscii3.txt" - RANDOM_BINARY_FILE1 = "randomBinary1.bin" - RANDOM_BINARY_FILE2 = "randomBinary2.bin" - - EMPTY_TEST_DIR = "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 ] - TEST_FILES = [ ASCII_TEST_FILES, BINARY_TEST_FILES, EMPTY_TEST_DIR ].flatten! - - def TestFiles.createTestFiles(recreate) - if (recreate || - ! (TEST_FILES.inject(true) { |accum, element| accum && File.exists?(element) })) - - ASCII_TEST_FILES.each_with_index { - |filename, index| - createRandomAscii(filename, 1E4 * (index+1)) - } - - BINARY_TEST_FILES.each_with_index { - |filename, index| - createRandomBinary(filename, 1E4 * (index+1)) - } - - ensureDir(EMPTY_TEST_DIR) - end - end - - private - def TestFiles.createRandomAscii(filename, size) - File.open(filename, "wb") { - |file| - while (file.tell < size) - file << rand - end - } - end - - def TestFiles.createRandomBinary(filename, size) - File.open(filename, "wb") { - |file| - while (file.tell < size) - file << rand.to_a.pack("V") - end - } - end - - def TestFiles.ensureDir(name) - if File.exists?(name) - return if File.stat(name).directory? - File.delete(name) - end - Dir.mkdir(name) - end - -end - -# For representation and creation of -# test data -class TestZipFile - attr_accessor :zipName, :entryNames, :comment - - def initialize(zipName, entryNames, comment = "") - @zipName=zipName - @entryNames=entryNames - @comment = comment - end - - def TestZipFile.createTestZips(recreate) - files = Dir.entries(".") - if (recreate || - ! (files.index(TEST_ZIP1.zipName) && - files.index(TEST_ZIP2.zipName) && - files.index(TEST_ZIP3.zipName) && - files.index(TEST_ZIP4.zipName) && - files.index("empty.txt") && - files.index("short.txt") && - files.index("longAscii.txt") && - files.index("longBinary.bin") )) - raise "failed to create test zip '#{TEST_ZIP1.zipName}'" unless - system("zip #{TEST_ZIP1.zipName} ziptest.rb") - raise "failed to remove entry from '#{TEST_ZIP1.zipName}'" unless - system("zip #{TEST_ZIP1.zipName} -d ziptest.rb") - - File.open("empty.txt", "w") {} - - File.open("short.txt", "w") { |file| file << "ABCDEF" } - ziptestTxt="" - File.open("ziptest.rb") { |file| ziptestTxt=file.read } - File.open("longAscii.txt", "w") { - |file| - while (file.tell < 1E5) - file << ziptestTxt - end - } - - testBinaryPattern="" - File.open("empty.zip") { |file| testBinaryPattern=file.read } - testBinaryPattern *= 4 - - File.open("longBinary.bin", "wb") { - |file| - while (file.tell < 3E5) - file << testBinaryPattern << rand - end - } - raise "failed to create test zip '#{TEST_ZIP2.zipName}'" unless - system("zip #{TEST_ZIP2.zipName} #{TEST_ZIP2.entryNames.join(' ')}") - - # without bash system interprets everything after echo as parameters to - # echo including | zip -z ... - raise "failed to add comment to test zip '#{TEST_ZIP2.zipName}'" unless - system("bash -c \"echo #{TEST_ZIP2.comment} | zip -z #{TEST_ZIP2.zipName}\"") - - raise "failed to create test zip '#{TEST_ZIP3.zipName}'" unless - system("zip #{TEST_ZIP3.zipName} #{TEST_ZIP3.entryNames.join(' ')}") - - raise "failed to create test zip '#{TEST_ZIP4.zipName}'" unless - system("zip #{TEST_ZIP4.zipName} #{TEST_ZIP4.entryNames.join(' ')}") - end - rescue - raise $!.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." - end - - TEST_ZIP1 = TestZipFile.new("empty.zip", []) - TEST_ZIP2 = TestZipFile.new("4entry.zip", %w{ longAscii.txt empty.txt short.txt longBinary.bin}, - "my zip comment") - TEST_ZIP3 = TestZipFile.new("test1.zip", %w{ file1.txt }) - TEST_ZIP4 = TestZipFile.new("zipWithDir.zip", [ "file1.txt", - TestFiles::EMPTY_TEST_DIR]) -end - - -class AbstractOutputStreamTest < RUNIT::TestCase - class TestOutputStream - include AbstractOutputStream - - attr_accessor :buffer - - def initialize - @buffer = "" - end - - def << (data) - @buffer << data - self - end - end - - def setup - @outputStream = TestOutputStream.new - - @origCommaSep = $, - @origOutputSep = $\ - end - - def teardown - $, = @origCommaSep - $\ = @origOutputSep - end - - def test_write - count = @outputStream.write("a little string") - assert_equals("a little string", @outputStream.buffer) - assert_equals("a little string".length, count) - - count = @outputStream.write(". a little more") - assert_equals("a little string. a little more", @outputStream.buffer) - assert_equals(". a little more".length, count) - end - - def test_print - $\ = nil # record separator set to nil - @outputStream.print("hello") - assert_equals("hello", @outputStream.buffer) - - @outputStream.print(" world.") - assert_equals("hello world.", @outputStream.buffer) - - @outputStream.print(" You ok ", "out ", "there?") - assert_equals("hello world. You ok out there?", @outputStream.buffer) - - $\ = "\n" - @outputStream.print - assert_equals("hello world. You ok out there?\n", @outputStream.buffer) - - @outputStream.print("I sure hope so!") - assert_equals("hello world. You ok out there?\nI sure hope so!\n", @outputStream.buffer) - - $, = "X" - @outputStream.buffer = "" - @outputStream.print("monkey", "duck", "zebra") - assert_equals("monkeyXduckXzebra\n", @outputStream.buffer) - - $\ = nil - @outputStream.buffer = "" - @outputStream.print(20) - assert_equals("20", @outputStream.buffer) - end - - def test_printf - @outputStream.printf("%d %04x", 123, 123) - assert_equals("123 007b", @outputStream.buffer) - end - - def test_putc - @outputStream.putc("A") - assert_equals("A", @outputStream.buffer) - @outputStream.putc(65) - assert_equals("AA", @outputStream.buffer) - end - - def test_puts - @outputStream.puts - assert_equals("\n", @outputStream.buffer) - - @outputStream.puts("hello", "world") - assert_equals("\nhello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts("hello\n", "world\n") - assert_equals("hello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(["hello\n", "world\n"]) - assert_equals("hello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(["hello\n", "world\n"], "bingo") - assert_equals("hello\nworld\nbingo\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(16, 20, 50, "hello") - assert_equals("16\n20\n50\nhello\n", @outputStream.buffer) - end -end - - -module CrcTest - def runCrcTest(compressorClass) - str = "Here's a nice little text to compute the crc for! Ho hum, it is nice nice nice nice indeed." - fakeOut = AbstractOutputStreamTest::TestOutputStream.new - - deflater = compressorClass.new(fakeOut) - deflater << str - assert_equals(0x919920fc, deflater.crc) - end -end - - - -class PassThruCompressorTest < RUNIT::TestCase - include CrcTest - - def test_size - File.open("dummy.txt", "wb") { - |file| - compressor = PassThruCompressor.new(file) - - assert_equals(0, compressor.size) - - t1 = "hello world" - t2 = "" - t3 = "bingo" - - compressor << t1 - assert_equals(compressor.size, t1.size) - - compressor << t2 - assert_equals(compressor.size, t1.size + t2.size) - - compressor << t3 - assert_equals(compressor.size, t1.size + t2.size + t3.size) - } - end - - def test_crc - runCrcTest(PassThruCompressor) - end -end - -class DeflaterTest < RUNIT::TestCase - include CrcTest - - def test_outputOperator - txt = loadFile("ziptest.rb") - deflate(txt, "deflatertest.bin") - inflatedTxt = inflate("deflatertest.bin") - assert_equals(txt, inflatedTxt) - end - - private - def loadFile(fileName) - txt = nil - File.open(fileName, "rb") { |f| txt = f.read } - end - - def deflate(data, fileName) - File.open(fileName, "wb") { - |file| - deflater = Deflater.new(file) - deflater << data - deflater.finish - assert_equals(deflater.size, data.size) - file << "trailing data for zlib with -MAX_WBITS" - } - end - - def inflate(fileName) - txt = nil - File.open(fileName, "rb") { - |file| - inflater = Inflater.new(file) - txt = inflater.read - } - end - - def test_crc - runCrcTest(Deflater) - end -end - -class ZipOutputStreamTest < RUNIT::TestCase - include AssertEntry - - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zipName = "output.zip" - - def test_new - zos = ZipOutputStream.new(TEST_ZIP.zipName) - zos.comment = TEST_ZIP.comment - writeTestZip(zos) - zos.close - assertTestZipContents(TEST_ZIP) - end - - def test_open - ZipOutputStream.open(TEST_ZIP.zipName) { - |zos| - zos.comment = TEST_ZIP.comment - writeTestZip(zos) - } - assertTestZipContents(TEST_ZIP) - end - - def test_writingToClosedStream - assertIOErrorInClosedStream { |zos| zos << "hello world" } - assertIOErrorInClosedStream { |zos| zos.puts "hello world" } - assertIOErrorInClosedStream { |zos| zos.write "hello world" } - end - - def test_cannotOpenFile - name = TestFiles::EMPTY_TEST_DIR - begin - zos = ZipOutputStream.open(name) - rescue Exception - 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: #{$!.type}") - end - end - - def assertIOErrorInClosedStream - assert_exception(IOError) { - zos = ZipOutputStream.new("test_putOnClosedStream.zip") - zos.close - yield zos - } - end - - def writeTestZip(zos) - TEST_ZIP.entryNames.each { - |entryName| - zos.putNextEntry(entryName) - File.open(entryName, "rb") { |f| zos.write(f.read) } - } - end -end - - - -module Enumerable - def compareEnumerables(otherEnumerable) - otherAsArray = otherEnumerable.to_a - index=0 - each_with_index { - |element, index| - return false unless yield(element, otherAsArray[index]) - } - return index+1 == otherAsArray.size - end -end - - -class ZipCentralDirectoryEntryTest < RUNIT::TestCase - - def test_readFromStream - File.open("testDirectory.bin", "rb") { - |file| - entry = ZipEntry.readCDirEntry(file) - - assert_equals("longAscii.txt", entry.name) - assert_equals(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equals(106490, entry.size) - assert_equals(3784, entry.compressedSize) - assert_equals(0xfcd1799c, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("empty.txt", entry.name) - assert_equals(ZipEntry::STORED, entry.compressionMethod) - assert_equals(0, entry.size) - assert_equals(0, entry.compressedSize) - assert_equals(0x0, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("short.txt", entry.name) - assert_equals(ZipEntry::STORED, entry.compressionMethod) - assert_equals(6, entry.size) - assert_equals(6, entry.compressedSize) - assert_equals(0xbb76fe69, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("longBinary.bin", entry.name) - assert_equals(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equals(1000024, entry.size) - assert_equals(70847, entry.compressedSize) - assert_equals(0x10da7d59, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals(nil, entry) -# Fields that are not check by this test: -# version made by 2 bytes -# version needed to extract 2 bytes -# general purpose bit flag 2 bytes -# last mod file time 2 bytes -# last mod file date 2 bytes -# compressed size 4 bytes -# uncompressed size 4 bytes -# disk number start 2 bytes -# internal file attributes 2 bytes -# external file attributes 4 bytes -# relative offset of local header 4 bytes - -# file name (variable size) -# extra field (variable size) -# file comment (variable size) - - } - end - - def test_ReadEntryFromTruncatedZipFile - fragment="" - File.open("testDirectory.bin") { |f| fragment = f.read(12) } # cdir entry header is at least 46 bytes - fragment.extend(IOizeString) - entry = ZipEntry.new - entry.readCDirEntry(fragment) - fail "ZipError expected" - rescue ZipError - end - -end - -class ZipCentralDirectoryTest < RUNIT::TestCase - - def test_readFromStream - File.open(TestZipFile::TEST_ZIP2.zipName, "rb") { - |zipFile| - cdir = ZipCentralDirectory.readFromStream(zipFile) - - assert_equals(TestZipFile::TEST_ZIP2.entryNames.size, cdir.size) - assert(cdir.compareEnumerables(TestZipFile::TEST_ZIP2.entryNames) { - |cdirEntry, testEntryName| - cdirEntry.name == testEntryName - }) - assert_equals(TestZipFile::TEST_ZIP2.comment, cdir.comment) - } - end - - def test_readFromInvalidStream - File.open("ziptest.rb", "rb") { - |zipFile| - cdir = ZipCentralDirectory.new - cdir.readFromStream(zipFile) - } - fail "ZipError expected!" - rescue ZipError - end - - def test_ReadFromTruncatedZipFile - fragment="" - File.open("testDirectory.bin") { |f| fragment = f.read } - fragment.slice!(12) # removed part of first cdir entry. eocd structure still complete - fragment.extend(IOizeString) - entry = ZipCentralDirectory.new - entry.readFromStream(fragment) - fail "ZipError expected" - rescue ZipError - end - - def test_writeToStream - entries = [ ZipEntry.new("file.zip", "flimse", "myComment", "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt", "Has a comment too") ] - cdir = ZipCentralDirectory.new(entries, "my zip comment") - File.open("cdirtest.bin", "wb") { |f| cdir.writeToStream(f) } - cdirReadback = ZipCentralDirectory.new - File.open("cdirtest.bin", "rb") { |f| cdirReadback.readFromStream(f) } - - assert_equals(cdir.entries, cdirReadback.entries) - end - - def test_equality - cdir1 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "my zip comment") - cdir2 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "my zip comment") - cdir3 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "comment?") - cdir4 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "comment?") - assert_equals(cdir1, cdir1) - assert_equals(cdir1, cdir2) - - assert(cdir1 != cdir3) - assert(cdir2 != cdir3) - assert(cdir2 != cdir3) - assert(cdir3 != cdir4) - - assert(cdir3 != "hello") - end -end - - -class BasicZipFileTest < RUNIT::TestCase - include AssertEntry - - def setup - @zipFile = ZipFile.new(TestZipFile::TEST_ZIP2.zipName) - @testEntryNameIndex=0 - end - - def nextTestEntryName - retVal=TestZipFile::TEST_ZIP2.entryNames[@testEntryNameIndex] - @testEntryNameIndex+=1 - return retVal - end - - def test_entries - assert_equals(TestZipFile::TEST_ZIP2.entryNames, @zipFile.entries.map {|e| e.name} ) - end - - def test_each - @zipFile.each { - |entry| - assert_equals(nextTestEntryName, entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_foreach - ZipFile.foreach(TestZipFile::TEST_ZIP2.zipName) { - |entry| - assert_equals(nextTestEntryName, entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_getInputStream - @zipFile.each { - |entry| - assertEntry(nextTestEntryName, @zipFile.getInputStream(entry), - entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_getInputStreamBlock - fileAndEntryName = @zipFile.entries.first.name - @zipFile.getInputStream(fileAndEntryName) { - |zis| - assertEntryContentsForStream(fileAndEntryName, - zis, - fileAndEntryName) - } - end -end - -class CommonZipFileFixture < RUNIT::TestCase - include AssertEntry - - EMPTY_FILENAME = "emptyZipFile.zip" - - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zipName = "4entry_copy.zip" - - def setup - File.delete(EMPTY_FILENAME) if File.exists?(EMPTY_FILENAME) - File.copy(TestZipFile::TEST_ZIP2.zipName, TEST_ZIP.zipName) - end -end - -class ZipFileTest < CommonZipFileFixture - - def test_createFromScratch - comment = "a short comment" - - zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) - zf.comment = comment - zf.close - - zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equals(comment, zfRead.comment) - assert_equals(0, zfRead.entries.length) - end - - def test_add - srcFile = "ziptest.rb" - entryName = "newEntryName.rb" - assert(File.exists? srcFile) - zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) - zf.add(entryName, srcFile) - zf.close - - zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equals("", zfRead.comment) - assert_equals(1, zfRead.entries.length) - assert_equals(entryName, zfRead.entries.first.name) - AssertEntry.assertContents(srcFile, - zfRead.getInputStream(entryName) { |zis| zis.read }) - end - - def test_addExistingEntryName - assert_exception(ZipEntryExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.add(zf.entries.first.name, "ziptest.rb") - } - } - end - - def test_addExistingEntryNameReplace - gotCalled = false - replacedEntry = nil - ZipFile.open(TEST_ZIP.zipName) { - |zf| - replacedEntry = zf.entries.first.name - zf.add(replacedEntry, "ziptest.rb") { gotCalled = true; true } - } - assert(gotCalled) - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assertContains(zf, replacedEntry, "ziptest.rb") - } - end - - def test_addDirectory - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.add(TestFiles::EMPTY_TEST_DIR, TestFiles::EMPTY_TEST_DIR) - } - ZipFile.open(TEST_ZIP.zipName) { - |zf| - dirEntry = zf.entries.detect { |e| e.name == TestFiles::EMPTY_TEST_DIR+"/" } - assert(dirEntry.isDirectory) - } - end - - def test_remove - entryToRemove, *remainingEntries = TEST_ZIP.entryNames - - File.copy(TestZipFile::TEST_ZIP2.zipName, TEST_ZIP.zipName) - - zf = ZipFile.new(TEST_ZIP.zipName) - assert(zf.entries.map { |e| e.name }.include?(entryToRemove)) - zf.remove(entryToRemove) - assert(! zf.entries.map { |e| e.name }.include?(entryToRemove)) - assert_equals(zf.entries.map {|x| x.name }.sort, remainingEntries.sort) - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(! zfRead.entries.map { |e| e.name }.include?(entryToRemove)) - assert_equals(zfRead.entries.map {|x| x.name }.sort, remainingEntries.sort) - zfRead.close - end - - - def test_rename - entryToRename, *remainingEntries = TEST_ZIP.entryNames - - zf = ZipFile.new(TEST_ZIP.zipName) - assert(zf.entries.map { |e| e.name }.include? entryToRename) - - newName = "changed name" - assert(! zf.entries.map { |e| e.name }.include?(newName)) - - zf.rename(entryToRename, newName) - assert(zf.entries.map { |e| e.name }.include? newName) - - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(zfRead.entries.map { |e| e.name }.include? newName) - zfRead.close - end - - def test_renameToExistingEntry - oldEntries = nil - ZipFile.open(TEST_ZIP.zipName) { |zf| oldEntries = zf.entries } - - assert_exception(ZipEntryExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.rename(zf.entries[0], zf.entries[1].name) - } - } - - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_equals(oldEntries.map{ |e| e.name }, zf.entries.map{ |e| e.name }) - } - end - - def test_renameToExistingEntryOverwrite - oldEntries = nil - ZipFile.open(TEST_ZIP.zipName) { |zf| oldEntries = zf.entries } - - gotCalled = false - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.rename(zf.entries[0], zf.entries[1].name) { gotCalled = true; true } - } - - assert(gotCalled) - oldEntries.delete_at(0) - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_equals(oldEntries.map{ |e| e.name }, - zf.entries.map{ |e| e.name }) - } - end - - def test_renameNonEntry - nonEntry = "bogusEntry" - targetEntry = "targetEntryName" - zf = ZipFile.new(TEST_ZIP.zipName) - assert(! zf.entries.include?(nonEntry)) - assert_exception(ZipNoSuchEntryError) { - zf.rename(nonEntry, targetEntry) - } - zf.commit - assert(! zf.entries.include?(targetEntry)) - ensure - zf.close - end - - def test_renameEntryToExistingEntry - entry1, entry2, *remaining = TEST_ZIP.entryNames - zf = ZipFile.new(TEST_ZIP.zipName) - assert_exception(ZipEntryExistsError) { - zf.rename(entry1, entry2) - } - ensure - zf.close - end - - def test_replace - unchangedEntries = TEST_ZIP.entryNames.dup - entryToReplace = unchangedEntries.delete_at(2) - newEntrySrcFilename = "ziptest.rb" - - zf = ZipFile.new(TEST_ZIP.zipName) - zf.replace(entryToReplace, newEntrySrcFilename) - - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - AssertEntry::assertContents(newEntrySrcFilename, - zfRead.getInputStream(entryToReplace) { |is| is.read }) - zfRead.close - end - - def test_replaceNonEntry - entryToReplace = "nonExistingEntryname" - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_exception(ZipNoSuchEntryError) { - zf.replace(entryToReplace, "ziptest.rb") - } - } - end - - def test_commit - newName = "renamedFirst" - zf = ZipFile.new(TEST_ZIP.zipName) - oldName = zf.entries.first - zf.rename(oldName, newName) - zf.commit - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(zfRead.entries.detect { |e| e.name == newName } != nil) - assert(zfRead.entries.detect { |e| e.name == oldName } == nil) - zfRead.close - - zf.close - end - - # This test tests that after commit, you - # can delete the file you used to add the entry to the zip file - # with - def test_commitUseZipEntry - File.copy(TestFiles::RANDOM_ASCII_FILE1, "okToDelete.txt") - zf = ZipFile.open(TEST_ZIP.zipName) - zf.add("okToDelete.txt", "okToDelete.txt") - assertContains(zf, "okToDelete.txt") - zf.commit - File.move("okToDelete.txt", "okToDeleteMoved.txt") - assertContains(zf, "okToDelete.txt", "okToDeleteMoved.txt") - end - -# def test_close -# zf = ZipFile.new(TEST_ZIP.zipName) -# zf.close -# assert_exception(IOError) { -# zf.extract(TEST_ZIP.entryNames.first, "hullubullu") -# } -# end - - def test_compound1 - renamedName = "renamedName" - originalEntries = [] - begin - zf = ZipFile.new(TEST_ZIP.zipName) - originalEntries = zf.entries.dup - - assertNotContains(zf, TestFiles::RANDOM_ASCII_FILE1) - zf.add(TestFiles::RANDOM_ASCII_FILE1, - TestFiles::RANDOM_ASCII_FILE1) - assertContains(zf, TestFiles::RANDOM_ASCII_FILE1) - - zf.rename(zf.entries[0], renamedName) - assertContains(zf, renamedName) - - TestFiles::BINARY_TEST_FILES.each { - |filename| - zf.add(filename, filename) - assertContains(zf, filename) - } - - assertContains(zf, originalEntries.last.to_s) - zf.remove(originalEntries.last.to_s) - assertNotContains(zf, originalEntries.last.to_s) - - ensure - zf.close - end - begin - zfRead = ZipFile.new(TEST_ZIP.zipName) - assertContains(zfRead, TestFiles::RANDOM_ASCII_FILE1) - assertContains(zfRead, renamedName) - TestFiles::BINARY_TEST_FILES.each { - |filename| - assertContains(zfRead, filename) - } - assertNotContains(zfRead, originalEntries.last.to_s) - ensure - zfRead.close - end - end - - def test_compound2 - begin - zf = ZipFile.new(TEST_ZIP.zipName) - originalEntries = zf.entries.dup - - originalEntries.each { - |entry| - zf.remove(entry) - assertNotContains(zf, entry) - } - assert(zf.entries.empty?) - - TestFiles::ASCII_TEST_FILES.each { - |filename| - zf.add(filename, filename) - assertContains(zf, filename) - } - assert_equals(zf.entries.map { |e| e.name }, TestFiles::ASCII_TEST_FILES) - - zf.rename(TestFiles::ASCII_TEST_FILES[0], "newName") - assertNotContains(zf, TestFiles::ASCII_TEST_FILES[0]) - assertContains(zf, "newName") - ensure - zf.close - end - begin - zfRead = ZipFile.new(TEST_ZIP.zipName) - asciiTestFiles = TestFiles::ASCII_TEST_FILES.dup - asciiTestFiles.shift - asciiTestFiles.each { - |filename| - assertContains(zf, filename) - } - - assertContains(zf, "newName") - ensure - zfRead.close - end - end - - private - def assertContains(zf, entryName, filename = entryName) - assert(zf.entries.detect { |e| e.name == entryName} != nil, "entry #{entryName} not in #{zf.entries.join(', ')} in zip file #{zf}") - assertEntryContents(zf, entryName, filename) if File.exists?(filename) - end - - def assertNotContains(zf, entryName) - assert(zf.entries.detect { |e| e.name == entryName} == nil, "entry #{entryName} in #{zf.entries.join(', ')} in zip file #{zf}") - end -end - -class ZipFileExtractTest < CommonZipFileFixture - EXTRACTED_FILENAME = "extEntry" - ENTRY_TO_EXTRACT, *REMAINING_ENTRIES = TEST_ZIP.entryNames.reverse - - def setup - super - File.delete(EXTRACTED_FILENAME) if File.exists?(EXTRACTED_FILENAME) - end - - def test_extract - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(ENTRY_TO_EXTRACT, EXTRACTED_FILENAME) - - assert(File.exists? EXTRACTED_FILENAME) - AssertEntry::assertContents(EXTRACTED_FILENAME, - zf.getInputStream(ENTRY_TO_EXTRACT) { |is| is.read }) - } - end - - def test_extractExists - writtenText = "written text" - File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - - assert_exception(ZipDestinationFileExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) - } - } - File.open(EXTRACTED_FILENAME, "r") { - |f| - assert_equals(writtenText, f.read) - } - end - - def test_extractExistsOverwrite - writtenText = "written text" - File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - - gotCalled = false - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) { gotCalled = true; true } - } - - assert(gotCalled) - File.open(EXTRACTED_FILENAME, "r") { - |f| - assert(writtenText != f.read) - } - end - - def test_extractNonEntry - zf = ZipFile.new(TEST_ZIP.zipName) - assert_exception(ZipNoSuchEntryError) { zf.extract("nonExistingEntry", "nonExistingEntry") } - ensure - zf.close if zf - end - - def test_extractNonEntry2 - outFile = "outfile" - assert_exception(ZipNoSuchEntryError) { - zf = ZipFile.new(TEST_ZIP.zipName) - nonEntry = "hotdog-diddelidoo" - assert(! zf.entries.include?(nonEntry)) - zf.extract(nonEntry, outFile) - zf.close - } - assert(! File.exists?(outFile)) - end - -end - -class ZipFileExtractDirectoryTest < CommonZipFileFixture - TEST_OUT_NAME = "emptyOutDir" - - def openZip(&aProc) - assert(aProc != nil) - ZipFile.open(TestZipFile::TEST_ZIP4.zipName, &aProc) - end - - def extractTestDir(&aProc) - openZip { - |zf| - zf.extract(TestFiles::EMPTY_TEST_DIR, TEST_OUT_NAME, &aProc) - } - end - - def setup - super - - Dir.rmdir(TEST_OUT_NAME) if File.directory? TEST_OUT_NAME - File.delete(TEST_OUT_NAME) if File.exists? TEST_OUT_NAME - end - - def test_extractDirectory - extractTestDir - assert(File.directory? TEST_OUT_NAME) - end - - def test_extractDirectoryExistsAsDir - Dir.mkdir TEST_OUT_NAME - extractTestDir - assert(File.directory? TEST_OUT_NAME) - end - - def test_extractDirectoryExistsAsFile - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - assert_exception(ZipDestinationFileExistsError) { extractTestDir } - end - - def test_extractDirectoryExistsAsFileOverwrite - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - gotCalled = false - extractTestDir { - |entry, destPath| - gotCalled = true - assert_equals(TEST_OUT_NAME, destPath) - assert(entry.isDirectory) - true - } - assert(gotCalled) - assert(File.directory? TEST_OUT_NAME) - end -end - - -TestFiles::createTestFiles(ARGV.index("recreate") != nil || - ARGV.index("recreateonly") != nil) -TestZipFile::createTestZips(ARGV.index("recreate") != nil || - ARGV.index("recreateonly") != nil) -exit if ARGV.index("recreateonly") != nil - -#require 'runit/cui/testrunner' -#RUNIT::CUI::TestRunner.run(ZipFileTest.suite) - -# Copyright (C) 2002 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. -#!/usr/bin/env ruby - -$VERBOSE = true - -require 'rubyunit' -require 'zip' - -include Zip - -Dir.chdir "test" - -class AbstractInputStreamTest < RUNIT::TestCase - # AbstractInputStream subclass that provides a read method - - TEST_LINES = [ "Hello world#{$/}", - "this is the second line#{$/}", - "this is the last line"] - TEST_STRING = TEST_LINES.join - class TestAbstractInputStream - include AbstractInputStream - def initialize(aString) - @contents = aString - @readPointer = 0 - end - - def read(charsToRead) - retVal=@contents[@readPointer, charsToRead] - @readPointer+=charsToRead - return retVal - end - - def produceInput - read(100) - end - - def inputFinished? - @contents[@readPointer] == nil - end - end - - def setup - @io = TestAbstractInputStream.new(TEST_STRING) - end - - def test_gets - assert_equals(TEST_LINES[0], @io.gets) - assert_equals(TEST_LINES[1], @io.gets) - assert_equals(TEST_LINES[2], @io.gets) - assert_equals(nil, @io.gets) - end - - def test_getsMultiCharSeperator - assert_equals("Hell", @io.gets("ll")) - assert_equals("o world#{$/}this is the second l", @io.gets("d l")) - end - - def test_each_line - lineNumber=0 - @io.each_line { - |line| - assert_equals(TEST_LINES[lineNumber], line) - lineNumber+=1 - } - end - - def test_readlines - assert_equals(TEST_LINES, @io.readlines) - end - - def test_readline - test_gets - begin - @io.readline - fail "EOFError expected" - rescue EOFError - end - end -end - -class ZipEntryTest < RUNIT::TestCase - TEST_ZIPFILE = "someZipFile.zip" - TEST_COMMENT = "a comment" - TEST_COMPRESSED_SIZE = 1234 - TEST_CRC = 325324 - TEST_EXTRA = "Some data here" - TEST_COMPRESSIONMETHOD = ZipEntry::DEFLATED - TEST_NAME = "entry name" - TEST_SIZE = 8432 - TEST_ISDIRECTORY = false - - def test_constructorAndGetters - entry = ZipEntry.new(TEST_ZIPFILE, - TEST_NAME, - TEST_COMMENT, - TEST_EXTRA, - TEST_COMPRESSED_SIZE, - TEST_CRC, - TEST_COMPRESSIONMETHOD, - TEST_SIZE) - - assert_equals(TEST_COMMENT, entry.comment) - assert_equals(TEST_COMPRESSED_SIZE, entry.compressedSize) - assert_equals(TEST_CRC, entry.crc) - assert_equals(TEST_EXTRA, entry.extra) - assert_equals(TEST_COMPRESSIONMETHOD, entry.compressionMethod) - assert_equals(TEST_NAME, entry.name) - assert_equals(TEST_SIZE, entry.size) - assert_equals(TEST_ISDIRECTORY, entry.isDirectory) - end - - def test_equality - entry1 = ZipEntry.new("file.zip", "name", "isNotCompared", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry2 = ZipEntry.new("file.zip", "name", "isNotComparedXXX", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry3 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry4 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry5 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 1234, - ZipEntry::DEFLATED, 10000) - entry6 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::DEFLATED, 10000) - entry7 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::STORED, 10000) - entry8 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::STORED, 100000) - - assert_equals(entry1, entry1) - assert_equals(entry1, entry2) - - assert(entry2 != entry3) - assert(entry3 != entry4) - assert(entry4 != entry5) - assert(entry5 != entry6) - assert(entry6 != entry7) - assert(entry7 != entry8) - - assert(entry7 != "hello") - assert(entry7 != 12) - end -end - -module IOizeString - attr_reader :tell - - def read(count = nil) - @tell ||= 0 - count = size unless count - retVal = slice(@tell, count) - @tell += count - return retVal - end - - def seek(index, offset) - @tell ||= 0 - case offset - when IO::SEEK_END - newPos = size + index - when IO::SEEK_SET - newPos = index - when IO::SEEK_CUR - newPos = @tell + index - else - raise "Error in test method IOizeString::seek" - end - if (newPos < 0 || newPos >= size) - raise Errno::EINVAL - else - @tell=newPos - end - end - - def reset - @tell = 0 - end -end - -class ZipLocalEntryTest < RUNIT::TestCase - def test_readLocalEntryHeaderOfFirstTestZipEntry - File.open(TestZipFile::TEST_ZIP3.zipName) { - |file| - entry = ZipEntry.readLocalEntry(file) - - assert_equal("", entry.comment) - # Differs from windows and unix because of CR LF - # assert_equal(480, entry.compressedSize) - # assert_equal(0x2a27930f, entry.crc) - # extra field is 21 bytes long - # probably contains some unix attrutes or something - # disabled: assert_equal(nil, entry.extra) - assert_equal(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equal(TestZipFile::TEST_ZIP3.entryNames[0], entry.name) - assert_equal(File.size(TestZipFile::TEST_ZIP3.entryNames[0]), entry.size) - assert(! entry.isDirectory) - } - end - - def test_readLocalEntryFromNonZipFile - File.open("ziptest.rb") { - |file| - assert_equals(nil, ZipEntry.readLocalEntry(file)) - } - end - - def test_readLocalEntryFromTruncatedZipFile - zipFragment="" - File.open(TestZipFile::TEST_ZIP2.zipName) { |f| zipFragment = f.read(12) } # local header is at least 30 bytes - zipFragment.extend(IOizeString).reset - entry = ZipEntry.new - entry.readLocalEntry(zipFragment) - fail "ZipError expected" - rescue ZipError - end - - def test_writeEntry - entry = ZipEntry.new("file.zip", "entryName", "my little comment", - "thisIsSomeExtraInformation", 100, 987654, - ZipEntry::DEFLATED, 400) - writeToFile("localEntryHeader.bin", "centralEntryHeader.bin", entry) - entryReadLocal, entryReadCentral = readFromFile("localEntryHeader.bin", "centralEntryHeader.bin") - compareLocalEntryHeaders(entry, entryReadLocal) - compareCDirEntryHeaders(entry, entryReadCentral) - end - - private - def compareLocalEntryHeaders(entry1, entry2) - assert_equals(entry1.compressedSize , entry2.compressedSize) - assert_equals(entry1.crc , entry2.crc) - assert_equals(entry1.extra , entry2.extra) - assert_equals(entry1.compressionMethod, entry2.compressionMethod) - assert_equals(entry1.name , entry2.name) - assert_equals(entry1.size , entry2.size) - assert_equals(entry1.localHeaderOffset, entry2.localHeaderOffset) - end - - def compareCDirEntryHeaders(entry1, entry2) - compareLocalEntryHeaders(entry1, entry2) - assert_equals(entry1.comment, entry2.comment) - end - - def writeToFile(localFileName, centralFileName, entry) - File.open(localFileName, "wb") { |f| entry.writeLocalEntry(f) } - File.open(centralFileName, "wb") { |f| entry.writeCDirEntry(f) } - end - - def readFromFile(localFileName, centralFileName) - localEntry = nil - cdirEntry = nil - File.open(localFileName, "rb") { |f| localEntry = ZipEntry.readLocalEntry(f) } - File.open(centralFileName, "rb") { |f| cdirEntry = ZipEntry.readCDirEntry(f) } - return [localEntry, cdirEntry] - end -end - - -module DecompressorTests - # expects @refText and @decompressor - - def test_readEverything - assert_equals(@refText, @decompressor.read) - end - - def test_readInChunks - chunkSize = 5 - while (decompressedChunk = @decompressor.read(chunkSize)) - assert_equals(@refText.slice!(0, chunkSize), decompressedChunk) - end - assert_equals(0, @refText.size) - end -end - -class InflaterTest < RUNIT::TestCase - include DecompressorTests - - def setup - @file = File.new("file1.txt.deflatedData", "rb") - @refText="" - File.open("file1.txt") { |f| @refText = f.read } - @decompressor = Inflater.new(@file) - end - - def teardown - @file.close - end -end - - -class PassThruDecompressorTest < RUNIT::TestCase - include DecompressorTests - TEST_FILE="file1.txt" - def setup - @file = File.new(TEST_FILE) - @refText="" - File.open(TEST_FILE) { |f| @refText = f.read } - @decompressor = PassThruDecompressor.new(@file, File.size(TEST_FILE)) - end - - def teardown - @file.close - end -end - - -module AssertEntry - def assertNextEntry(filename, zis) - assertEntry(filename, zis, zis.getNextEntry.name) - end - - def assertEntry(filename, zis, entryName) - assert_equals(filename, entryName) - assertEntryContentsForStream(filename, zis, entryName) - end - - def assertEntryContentsForStream(filename, zis, entryName) - File.open(filename, "rb") { - |file| - expected = file.read - actual = zis.read - if (expected != actual) - if (expected.length > 400 || actual.length > 400) - zipEntryFilename=entryName+".zipEntry" - File.open(zipEntryFilename, "wb") { |file| file << actual } - fail("File '#{filename}' is different from '#{zipEntryFilename}'") - else - assert_equals(expected, actual) - end - end - } - end - - def AssertEntry.assertContents(filename, aString) - fileContents = "" - File.open(filename, "rb") { |f| fileContents = f.read } - if (fileContents != aString) - if (expected.length > 400 || actual.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_equals(expected, actual) - end - end - end - - def assertStreamContents(zis, testZipFile) - assert(zis != nil) - testZipFile.entryNames.each { - |entryName| - assertNextEntry(entryName, zis) - } - assert_equals(nil, zis.getNextEntry) - end - - def assertTestZipContents(testZipFile) - ZipInputStream.open(testZipFile.zipName) { - |zis| - assertStreamContents(zis, testZipFile) - } - end - - def assertEntryContents(zipFile, entryName, filename = entryName.to_s) - zis = zipFile.getInputStream(entryName) - assertEntryContentsForStream(filename, zis, entryName) - ensure - zis.close if zis - end -end - - - -class ZipInputStreamTest < RUNIT::TestCase - include AssertEntry - - def test_new - zis = ZipInputStream.new(TestZipFile::TEST_ZIP2.zipName) - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - zis.close - end - - def test_openWithBlock - ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) { - |zis| - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - } - end - - def test_openWithoutBlock - zis = ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - end - - def test_incompleteReads - ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) { - |zis| - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[0], entry.name) - assert zis.gets.length > 0 - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[1], entry.name) - assert_equals(0, entry.size) - assert_equals(nil, zis.gets) - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[2], entry.name) - assert zis.gets.length > 0 - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[3], entry.name) - assert zis.gets.length > 0 - } - end - -end - -class TestFiles - RANDOM_ASCII_FILE1 = "randomAscii1.txt" - RANDOM_ASCII_FILE2 = "randomAscii2.txt" - RANDOM_ASCII_FILE3 = "randomAscii3.txt" - RANDOM_BINARY_FILE1 = "randomBinary1.bin" - RANDOM_BINARY_FILE2 = "randomBinary2.bin" - - EMPTY_TEST_DIR = "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 ] - TEST_FILES = [ ASCII_TEST_FILES, BINARY_TEST_FILES, EMPTY_TEST_DIR ].flatten! - - def TestFiles.createTestFiles(recreate) - if (recreate || - ! (TEST_FILES.inject(true) { |accum, element| accum && File.exists?(element) })) - - ASCII_TEST_FILES.each_with_index { - |filename, index| - createRandomAscii(filename, 1E4 * (index+1)) - } - - BINARY_TEST_FILES.each_with_index { - |filename, index| - createRandomBinary(filename, 1E4 * (index+1)) - } - - ensureDir(EMPTY_TEST_DIR) - end - end - - private - def TestFiles.createRandomAscii(filename, size) - File.open(filename, "wb") { - |file| - while (file.tell < size) - file << rand - end - } - end - - def TestFiles.createRandomBinary(filename, size) - File.open(filename, "wb") { - |file| - while (file.tell < size) - file << rand.to_a.pack("V") - end - } - end - - def TestFiles.ensureDir(name) - if File.exists?(name) - return if File.stat(name).directory? - File.delete(name) - end - Dir.mkdir(name) - end - -end - -# For representation and creation of -# test data -class TestZipFile - attr_accessor :zipName, :entryNames, :comment - - def initialize(zipName, entryNames, comment = "") - @zipName=zipName - @entryNames=entryNames - @comment = comment - end - - def TestZipFile.createTestZips(recreate) - files = Dir.entries(".") - if (recreate || - ! (files.index(TEST_ZIP1.zipName) && - files.index(TEST_ZIP2.zipName) && - files.index(TEST_ZIP3.zipName) && - files.index(TEST_ZIP4.zipName) && - files.index("empty.txt") && - files.index("short.txt") && - files.index("longAscii.txt") && - files.index("longBinary.bin") )) - raise "failed to create test zip '#{TEST_ZIP1.zipName}'" unless - system("zip #{TEST_ZIP1.zipName} ziptest.rb") - raise "failed to remove entry from '#{TEST_ZIP1.zipName}'" unless - system("zip #{TEST_ZIP1.zipName} -d ziptest.rb") - - File.open("empty.txt", "w") {} - - File.open("short.txt", "w") { |file| file << "ABCDEF" } - ziptestTxt="" - File.open("ziptest.rb") { |file| ziptestTxt=file.read } - File.open("longAscii.txt", "w") { - |file| - while (file.tell < 1E5) - file << ziptestTxt - end - } - - testBinaryPattern="" - File.open("empty.zip") { |file| testBinaryPattern=file.read } - testBinaryPattern *= 4 - - File.open("longBinary.bin", "wb") { - |file| - while (file.tell < 3E5) - file << testBinaryPattern << rand - end - } - raise "failed to create test zip '#{TEST_ZIP2.zipName}'" unless - system("zip #{TEST_ZIP2.zipName} #{TEST_ZIP2.entryNames.join(' ')}") - - # without bash system interprets everything after echo as parameters to - # echo including | zip -z ... - raise "failed to add comment to test zip '#{TEST_ZIP2.zipName}'" unless - system("bash -c \"echo #{TEST_ZIP2.comment} | zip -z #{TEST_ZIP2.zipName}\"") - - raise "failed to create test zip '#{TEST_ZIP3.zipName}'" unless - system("zip #{TEST_ZIP3.zipName} #{TEST_ZIP3.entryNames.join(' ')}") - - raise "failed to create test zip '#{TEST_ZIP4.zipName}'" unless - system("zip #{TEST_ZIP4.zipName} #{TEST_ZIP4.entryNames.join(' ')}") - end - rescue - raise $!.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." - end - - TEST_ZIP1 = TestZipFile.new("empty.zip", []) - TEST_ZIP2 = TestZipFile.new("4entry.zip", %w{ longAscii.txt empty.txt short.txt longBinary.bin}, - "my zip comment") - TEST_ZIP3 = TestZipFile.new("test1.zip", %w{ file1.txt }) - TEST_ZIP4 = TestZipFile.new("zipWithDir.zip", [ "file1.txt", - TestFiles::EMPTY_TEST_DIR]) -end - - -class AbstractOutputStreamTest < RUNIT::TestCase - class TestOutputStream - include AbstractOutputStream - - attr_accessor :buffer - - def initialize - @buffer = "" - end - - def << (data) - @buffer << data - self - end - end - - def setup - @outputStream = TestOutputStream.new - - @origCommaSep = $, - @origOutputSep = $\ - end - - def teardown - $, = @origCommaSep - $\ = @origOutputSep - end - - def test_write - count = @outputStream.write("a little string") - assert_equals("a little string", @outputStream.buffer) - assert_equals("a little string".length, count) - - count = @outputStream.write(". a little more") - assert_equals("a little string. a little more", @outputStream.buffer) - assert_equals(". a little more".length, count) - end - - def test_print - $\ = nil # record separator set to nil - @outputStream.print("hello") - assert_equals("hello", @outputStream.buffer) - - @outputStream.print(" world.") - assert_equals("hello world.", @outputStream.buffer) - - @outputStream.print(" You ok ", "out ", "there?") - assert_equals("hello world. You ok out there?", @outputStream.buffer) - - $\ = "\n" - @outputStream.print - assert_equals("hello world. You ok out there?\n", @outputStream.buffer) - - @outputStream.print("I sure hope so!") - assert_equals("hello world. You ok out there?\nI sure hope so!\n", @outputStream.buffer) - - $, = "X" - @outputStream.buffer = "" - @outputStream.print("monkey", "duck", "zebra") - assert_equals("monkeyXduckXzebra\n", @outputStream.buffer) - - $\ = nil - @outputStream.buffer = "" - @outputStream.print(20) - assert_equals("20", @outputStream.buffer) - end - - def test_printf - @outputStream.printf("%d %04x", 123, 123) - assert_equals("123 007b", @outputStream.buffer) - end - - def test_putc - @outputStream.putc("A") - assert_equals("A", @outputStream.buffer) - @outputStream.putc(65) - assert_equals("AA", @outputStream.buffer) - end - - def test_puts - @outputStream.puts - assert_equals("\n", @outputStream.buffer) - - @outputStream.puts("hello", "world") - assert_equals("\nhello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts("hello\n", "world\n") - assert_equals("hello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(["hello\n", "world\n"]) - assert_equals("hello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(["hello\n", "world\n"], "bingo") - assert_equals("hello\nworld\nbingo\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(16, 20, 50, "hello") - assert_equals("16\n20\n50\nhello\n", @outputStream.buffer) - end -end - - -module CrcTest - def runCrcTest(compressorClass) - str = "Here's a nice little text to compute the crc for! Ho hum, it is nice nice nice nice indeed." - fakeOut = AbstractOutputStreamTest::TestOutputStream.new - - deflater = compressorClass.new(fakeOut) - deflater << str - assert_equals(0x919920fc, deflater.crc) - end -end - - - -class PassThruCompressorTest < RUNIT::TestCase - include CrcTest - - def test_size - File.open("dummy.txt", "wb") { - |file| - compressor = PassThruCompressor.new(file) - - assert_equals(0, compressor.size) - - t1 = "hello world" - t2 = "" - t3 = "bingo" - - compressor << t1 - assert_equals(compressor.size, t1.size) - - compressor << t2 - assert_equals(compressor.size, t1.size + t2.size) - - compressor << t3 - assert_equals(compressor.size, t1.size + t2.size + t3.size) - } - end - - def test_crc - runCrcTest(PassThruCompressor) - end -end - -class DeflaterTest < RUNIT::TestCase - include CrcTest - - def test_outputOperator - txt = loadFile("ziptest.rb") - deflate(txt, "deflatertest.bin") - inflatedTxt = inflate("deflatertest.bin") - assert_equals(txt, inflatedTxt) - end - - private - def loadFile(fileName) - txt = nil - File.open(fileName, "rb") { |f| txt = f.read } - end - - def deflate(data, fileName) - File.open(fileName, "wb") { - |file| - deflater = Deflater.new(file) - deflater << data - deflater.finish - assert_equals(deflater.size, data.size) - file << "trailing data for zlib with -MAX_WBITS" - } - end - - def inflate(fileName) - txt = nil - File.open(fileName, "rb") { - |file| - inflater = Inflater.new(file) - txt = inflater.read - } - end - - def test_crc - runCrcTest(Deflater) - end -end - -class ZipOutputStreamTest < RUNIT::TestCase - include AssertEntry - - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zipName = "output.zip" - - def test_new - zos = ZipOutputStream.new(TEST_ZIP.zipName) - zos.comment = TEST_ZIP.comment - writeTestZip(zos) - zos.close - assertTestZipContents(TEST_ZIP) - end - - def test_open - ZipOutputStream.open(TEST_ZIP.zipName) { - |zos| - zos.comment = TEST_ZIP.comment - writeTestZip(zos) - } - assertTestZipContents(TEST_ZIP) - end - - def test_writingToClosedStream - assertIOErrorInClosedStream { |zos| zos << "hello world" } - assertIOErrorInClosedStream { |zos| zos.puts "hello world" } - assertIOErrorInClosedStream { |zos| zos.write "hello world" } - end - - def test_cannotOpenFile - name = TestFiles::EMPTY_TEST_DIR - begin - zos = ZipOutputStream.open(name) - rescue Exception - 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: #{$!.type}") - end - end - - def assertIOErrorInClosedStream - assert_exception(IOError) { - zos = ZipOutputStream.new("test_putOnClosedStream.zip") - zos.close - yield zos - } - end - - def writeTestZip(zos) - TEST_ZIP.entryNames.each { - |entryName| - zos.putNextEntry(entryName) - File.open(entryName, "rb") { |f| zos.write(f.read) } - } - end -end - - - -module Enumerable - def compareEnumerables(otherEnumerable) - otherAsArray = otherEnumerable.to_a - index=0 - each_with_index { - |element, index| - return false unless yield(element, otherAsArray[index]) - } - return index+1 == otherAsArray.size - end -end - - -class ZipCentralDirectoryEntryTest < RUNIT::TestCase - - def test_readFromStream - File.open("testDirectory.bin", "rb") { - |file| - entry = ZipEntry.readCDirEntry(file) - - assert_equals("longAscii.txt", entry.name) - assert_equals(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equals(106490, entry.size) - assert_equals(3784, entry.compressedSize) - assert_equals(0xfcd1799c, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("empty.txt", entry.name) - assert_equals(ZipEntry::STORED, entry.compressionMethod) - assert_equals(0, entry.size) - assert_equals(0, entry.compressedSize) - assert_equals(0x0, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("short.txt", entry.name) - assert_equals(ZipEntry::STORED, entry.compressionMethod) - assert_equals(6, entry.size) - assert_equals(6, entry.compressedSize) - assert_equals(0xbb76fe69, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("longBinary.bin", entry.name) - assert_equals(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equals(1000024, entry.size) - assert_equals(70847, entry.compressedSize) - assert_equals(0x10da7d59, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals(nil, entry) -# Fields that are not check by this test: -# version made by 2 bytes -# version needed to extract 2 bytes -# general purpose bit flag 2 bytes -# last mod file time 2 bytes -# last mod file date 2 bytes -# compressed size 4 bytes -# uncompressed size 4 bytes -# disk number start 2 bytes -# internal file attributes 2 bytes -# external file attributes 4 bytes -# relative offset of local header 4 bytes - -# file name (variable size) -# extra field (variable size) -# file comment (variable size) - - } - end - - def test_ReadEntryFromTruncatedZipFile - fragment="" - File.open("testDirectory.bin") { |f| fragment = f.read(12) } # cdir entry header is at least 46 bytes - fragment.extend(IOizeString) - entry = ZipEntry.new - entry.readCDirEntry(fragment) - fail "ZipError expected" - rescue ZipError - end - -end - -class ZipCentralDirectoryTest < RUNIT::TestCase - - def test_readFromStream - File.open(TestZipFile::TEST_ZIP2.zipName, "rb") { - |zipFile| - cdir = ZipCentralDirectory.readFromStream(zipFile) - - assert_equals(TestZipFile::TEST_ZIP2.entryNames.size, cdir.size) - assert(cdir.compareEnumerables(TestZipFile::TEST_ZIP2.entryNames) { - |cdirEntry, testEntryName| - cdirEntry.name == testEntryName - }) - assert_equals(TestZipFile::TEST_ZIP2.comment, cdir.comment) - } - end - - def test_readFromInvalidStream - File.open("ziptest.rb", "rb") { - |zipFile| - cdir = ZipCentralDirectory.new - cdir.readFromStream(zipFile) - } - fail "ZipError expected!" - rescue ZipError - end - - def test_ReadFromTruncatedZipFile - fragment="" - File.open("testDirectory.bin") { |f| fragment = f.read } - fragment.slice!(12) # removed part of first cdir entry. eocd structure still complete - fragment.extend(IOizeString) - entry = ZipCentralDirectory.new - entry.readFromStream(fragment) - fail "ZipError expected" - rescue ZipError - end - - def test_writeToStream - entries = [ ZipEntry.new("file.zip", "flimse", "myComment", "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt", "Has a comment too") ] - cdir = ZipCentralDirectory.new(entries, "my zip comment") - File.open("cdirtest.bin", "wb") { |f| cdir.writeToStream(f) } - cdirReadback = ZipCentralDirectory.new - File.open("cdirtest.bin", "rb") { |f| cdirReadback.readFromStream(f) } - - assert_equals(cdir.entries, cdirReadback.entries) - end - - def test_equality - cdir1 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "my zip comment") - cdir2 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "my zip comment") - cdir3 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "comment?") - cdir4 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "comment?") - assert_equals(cdir1, cdir1) - assert_equals(cdir1, cdir2) - - assert(cdir1 != cdir3) - assert(cdir2 != cdir3) - assert(cdir2 != cdir3) - assert(cdir3 != cdir4) - - assert(cdir3 != "hello") - end -end - - -class BasicZipFileTest < RUNIT::TestCase - include AssertEntry - - def setup - @zipFile = ZipFile.new(TestZipFile::TEST_ZIP2.zipName) - @testEntryNameIndex=0 - end - - def nextTestEntryName - retVal=TestZipFile::TEST_ZIP2.entryNames[@testEntryNameIndex] - @testEntryNameIndex+=1 - return retVal - end - - def test_entries - assert_equals(TestZipFile::TEST_ZIP2.entryNames, @zipFile.entries.map {|e| e.name} ) - end - - def test_each - @zipFile.each { - |entry| - assert_equals(nextTestEntryName, entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_foreach - ZipFile.foreach(TestZipFile::TEST_ZIP2.zipName) { - |entry| - assert_equals(nextTestEntryName, entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_getInputStream - @zipFile.each { - |entry| - assertEntry(nextTestEntryName, @zipFile.getInputStream(entry), - entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_getInputStreamBlock - fileAndEntryName = @zipFile.entries.first.name - @zipFile.getInputStream(fileAndEntryName) { - |zis| - assertEntryContentsForStream(fileAndEntryName, - zis, - fileAndEntryName) - } - end -end - -class CommonZipFileFixture < RUNIT::TestCase - include AssertEntry - - EMPTY_FILENAME = "emptyZipFile.zip" - - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zipName = "4entry_copy.zip" - - def setup - File.delete(EMPTY_FILENAME) if File.exists?(EMPTY_FILENAME) - File.copy(TestZipFile::TEST_ZIP2.zipName, TEST_ZIP.zipName) - end -end - -class ZipFileTest < CommonZipFileFixture - - def test_createFromScratch - comment = "a short comment" - - zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) - zf.comment = comment - zf.close - - zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equals(comment, zfRead.comment) - assert_equals(0, zfRead.entries.length) - end - - def test_add - srcFile = "ziptest.rb" - entryName = "newEntryName.rb" - assert(File.exists? srcFile) - zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) - zf.add(entryName, srcFile) - zf.close - - zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equals("", zfRead.comment) - assert_equals(1, zfRead.entries.length) - assert_equals(entryName, zfRead.entries.first.name) - AssertEntry.assertContents(srcFile, - zfRead.getInputStream(entryName) { |zis| zis.read }) - end - - def test_addExistingEntryName - assert_exception(ZipEntryExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.add(zf.entries.first.name, "ziptest.rb") - } - } - end - - def test_addExistingEntryNameReplace - gotCalled = false - replacedEntry = nil - ZipFile.open(TEST_ZIP.zipName) { - |zf| - replacedEntry = zf.entries.first.name - zf.add(replacedEntry, "ziptest.rb") { gotCalled = true; true } - } - assert(gotCalled) - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assertContains(zf, replacedEntry, "ziptest.rb") - } - end - - def test_addDirectory - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.add(TestFiles::EMPTY_TEST_DIR, TestFiles::EMPTY_TEST_DIR) - } - ZipFile.open(TEST_ZIP.zipName) { - |zf| - dirEntry = zf.entries.detect { |e| e.name == TestFiles::EMPTY_TEST_DIR+"/" } - assert(dirEntry.isDirectory) - } - end - - def test_remove - entryToRemove, *remainingEntries = TEST_ZIP.entryNames - - File.copy(TestZipFile::TEST_ZIP2.zipName, TEST_ZIP.zipName) - - zf = ZipFile.new(TEST_ZIP.zipName) - assert(zf.entries.map { |e| e.name }.include?(entryToRemove)) - zf.remove(entryToRemove) - assert(! zf.entries.map { |e| e.name }.include?(entryToRemove)) - assert_equals(zf.entries.map {|x| x.name }.sort, remainingEntries.sort) - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(! zfRead.entries.map { |e| e.name }.include?(entryToRemove)) - assert_equals(zfRead.entries.map {|x| x.name }.sort, remainingEntries.sort) - zfRead.close - end - - - def test_rename - entryToRename, *remainingEntries = TEST_ZIP.entryNames - - zf = ZipFile.new(TEST_ZIP.zipName) - assert(zf.entries.map { |e| e.name }.include? entryToRename) - - newName = "changed name" - assert(! zf.entries.map { |e| e.name }.include?(newName)) - - zf.rename(entryToRename, newName) - assert(zf.entries.map { |e| e.name }.include? newName) - - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(zfRead.entries.map { |e| e.name }.include? newName) - zfRead.close - end - - def test_renameToExistingEntry - oldEntries = nil - ZipFile.open(TEST_ZIP.zipName) { |zf| oldEntries = zf.entries } - - assert_exception(ZipEntryExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.rename(zf.entries[0], zf.entries[1].name) - } - } - - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_equals(oldEntries.map{ |e| e.name }, zf.entries.map{ |e| e.name }) - } - end - - def test_renameToExistingEntryOverwrite - oldEntries = nil - ZipFile.open(TEST_ZIP.zipName) { |zf| oldEntries = zf.entries } - - gotCalled = false - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.rename(zf.entries[0], zf.entries[1].name) { gotCalled = true; true } - } - - assert(gotCalled) - oldEntries.delete_at(0) - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_equals(oldEntries.map{ |e| e.name }, - zf.entries.map{ |e| e.name }) - } - end - - def test_renameNonEntry - nonEntry = "bogusEntry" - targetEntry = "targetEntryName" - zf = ZipFile.new(TEST_ZIP.zipName) - assert(! zf.entries.include?(nonEntry)) - assert_exception(ZipNoSuchEntryError) { - zf.rename(nonEntry, targetEntry) - } - zf.commit - assert(! zf.entries.include?(targetEntry)) - ensure - zf.close - end - - def test_renameEntryToExistingEntry - entry1, entry2, *remaining = TEST_ZIP.entryNames - zf = ZipFile.new(TEST_ZIP.zipName) - assert_exception(ZipEntryExistsError) { - zf.rename(entry1, entry2) - } - ensure - zf.close - end - - def test_replace - unchangedEntries = TEST_ZIP.entryNames.dup - entryToReplace = unchangedEntries.delete_at(2) - newEntrySrcFilename = "ziptest.rb" - - zf = ZipFile.new(TEST_ZIP.zipName) - zf.replace(entryToReplace, newEntrySrcFilename) - - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - AssertEntry::assertContents(newEntrySrcFilename, - zfRead.getInputStream(entryToReplace) { |is| is.read }) - zfRead.close - end - - def test_replaceNonEntry - entryToReplace = "nonExistingEntryname" - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_exception(ZipNoSuchEntryError) { - zf.replace(entryToReplace, "ziptest.rb") - } - } - end - - def test_commit - newName = "renamedFirst" - zf = ZipFile.new(TEST_ZIP.zipName) - oldName = zf.entries.first - zf.rename(oldName, newName) - zf.commit - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(zfRead.entries.detect { |e| e.name == newName } != nil) - assert(zfRead.entries.detect { |e| e.name == oldName } == nil) - zfRead.close - - zf.close - end - - # This test tests that after commit, you - # can delete the file you used to add the entry to the zip file - # with - def test_commitUseZipEntry - File.copy(TestFiles::RANDOM_ASCII_FILE1, "okToDelete.txt") - zf = ZipFile.open(TEST_ZIP.zipName) - zf.add("okToDelete.txt", "okToDelete.txt") - assertContains(zf, "okToDelete.txt") - zf.commit - File.move("okToDelete.txt", "okToDeleteMoved.txt") - assertContains(zf, "okToDelete.txt", "okToDeleteMoved.txt") - end - -# def test_close -# zf = ZipFile.new(TEST_ZIP.zipName) -# zf.close -# assert_exception(IOError) { -# zf.extract(TEST_ZIP.entryNames.first, "hullubullu") -# } -# end - - def test_compound1 - renamedName = "renamedName" - originalEntries = [] - begin - zf = ZipFile.new(TEST_ZIP.zipName) - originalEntries = zf.entries.dup - - assertNotContains(zf, TestFiles::RANDOM_ASCII_FILE1) - zf.add(TestFiles::RANDOM_ASCII_FILE1, - TestFiles::RANDOM_ASCII_FILE1) - assertContains(zf, TestFiles::RANDOM_ASCII_FILE1) - - zf.rename(zf.entries[0], renamedName) - assertContains(zf, renamedName) - - TestFiles::BINARY_TEST_FILES.each { - |filename| - zf.add(filename, filename) - assertContains(zf, filename) - } - - assertContains(zf, originalEntries.last.to_s) - zf.remove(originalEntries.last.to_s) - assertNotContains(zf, originalEntries.last.to_s) - - ensure - zf.close - end - begin - zfRead = ZipFile.new(TEST_ZIP.zipName) - assertContains(zfRead, TestFiles::RANDOM_ASCII_FILE1) - assertContains(zfRead, renamedName) - TestFiles::BINARY_TEST_FILES.each { - |filename| - assertContains(zfRead, filename) - } - assertNotContains(zfRead, originalEntries.last.to_s) - ensure - zfRead.close - end - end - - def test_compound2 - begin - zf = ZipFile.new(TEST_ZIP.zipName) - originalEntries = zf.entries.dup - - originalEntries.each { - |entry| - zf.remove(entry) - assertNotContains(zf, entry) - } - assert(zf.entries.empty?) - - TestFiles::ASCII_TEST_FILES.each { - |filename| - zf.add(filename, filename) - assertContains(zf, filename) - } - assert_equals(zf.entries.map { |e| e.name }, TestFiles::ASCII_TEST_FILES) - - zf.rename(TestFiles::ASCII_TEST_FILES[0], "newName") - assertNotContains(zf, TestFiles::ASCII_TEST_FILES[0]) - assertContains(zf, "newName") - ensure - zf.close - end - begin - zfRead = ZipFile.new(TEST_ZIP.zipName) - asciiTestFiles = TestFiles::ASCII_TEST_FILES.dup - asciiTestFiles.shift - asciiTestFiles.each { - |filename| - assertContains(zf, filename) - } - - assertContains(zf, "newName") - ensure - zfRead.close - end - end - - private - def assertContains(zf, entryName, filename = entryName) - assert(zf.entries.detect { |e| e.name == entryName} != nil, "entry #{entryName} not in #{zf.entries.join(', ')} in zip file #{zf}") - assertEntryContents(zf, entryName, filename) if File.exists?(filename) - end - - def assertNotContains(zf, entryName) - assert(zf.entries.detect { |e| e.name == entryName} == nil, "entry #{entryName} in #{zf.entries.join(', ')} in zip file #{zf}") - end -end - -class ZipFileExtractTest < CommonZipFileFixture - EXTRACTED_FILENAME = "extEntry" - ENTRY_TO_EXTRACT, *REMAINING_ENTRIES = TEST_ZIP.entryNames.reverse - - def setup - super - File.delete(EXTRACTED_FILENAME) if File.exists?(EXTRACTED_FILENAME) - end - - def test_extract - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(ENTRY_TO_EXTRACT, EXTRACTED_FILENAME) - - assert(File.exists? EXTRACTED_FILENAME) - AssertEntry::assertContents(EXTRACTED_FILENAME, - zf.getInputStream(ENTRY_TO_EXTRACT) { |is| is.read }) - } - end - - def test_extractExists - writtenText = "written text" - File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - - assert_exception(ZipDestinationFileExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) - } - } - File.open(EXTRACTED_FILENAME, "r") { - |f| - assert_equals(writtenText, f.read) - } - end - - def test_extractExistsOverwrite - writtenText = "written text" - File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - - gotCalled = false - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) { gotCalled = true; true } - } - - assert(gotCalled) - File.open(EXTRACTED_FILENAME, "r") { - |f| - assert(writtenText != f.read) - } - end - - def test_extractNonEntry - zf = ZipFile.new(TEST_ZIP.zipName) - assert_exception(ZipNoSuchEntryError) { zf.extract("nonExistingEntry", "nonExistingEntry") } - ensure - zf.close if zf - end - - def test_extractNonEntry2 - outFile = "outfile" - assert_exception(ZipNoSuchEntryError) { - zf = ZipFile.new(TEST_ZIP.zipName) - nonEntry = "hotdog-diddelidoo" - assert(! zf.entries.include?(nonEntry)) - zf.extract(nonEntry, outFile) - zf.close - } - assert(! File.exists?(outFile)) - end - -end - -class ZipFileExtractDirectoryTest < CommonZipFileFixture - TEST_OUT_NAME = "emptyOutDir" - - def openZip(&aProc) - assert(aProc != nil) - ZipFile.open(TestZipFile::TEST_ZIP4.zipName, &aProc) - end - - def extractTestDir(&aProc) - openZip { - |zf| - zf.extract(TestFiles::EMPTY_TEST_DIR, TEST_OUT_NAME, &aProc) - } - end - - def setup - super - - Dir.rmdir(TEST_OUT_NAME) if File.directory? TEST_OUT_NAME - File.delete(TEST_OUT_NAME) if File.exists? TEST_OUT_NAME - end - - def test_extractDirectory - extractTestDir - assert(File.directory? TEST_OUT_NAME) - end - - def test_extractDirectoryExistsAsDir - Dir.mkdir TEST_OUT_NAME - extractTestDir - assert(File.directory? TEST_OUT_NAME) - end - - def test_extractDirectoryExistsAsFile - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - assert_exception(ZipDestinationFileExistsError) { extractTestDir } - end - - def test_extractDirectoryExistsAsFileOverwrite - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - gotCalled = false - extractTestDir { - |entry, destPath| - gotCalled = true - assert_equals(TEST_OUT_NAME, destPath) - assert(entry.isDirectory) - true - } - assert(gotCalled) - assert(File.directory? TEST_OUT_NAME) - end -end - - -TestFiles::createTestFiles(ARGV.index("recreate") != nil || - ARGV.index("recreateonly") != nil) -TestZipFile::createTestZips(ARGV.index("recreate") != nil || - ARGV.index("recreateonly") != nil) -exit if ARGV.index("recreateonly") != nil - -#require 'runit/cui/testrunner' -#RUNIT::CUI::TestRunner.run(ZipFileTest.suite) - -# Copyright (C) 2002 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. -#!/usr/bin/env ruby - -$VERBOSE = true - -require 'rubyunit' -require 'zip' - -include Zip - -Dir.chdir "test" - -class AbstractInputStreamTest < RUNIT::TestCase - # AbstractInputStream subclass that provides a read method - - TEST_LINES = [ "Hello world#{$/}", - "this is the second line#{$/}", - "this is the last line"] - TEST_STRING = TEST_LINES.join - class TestAbstractInputStream - include AbstractInputStream - def initialize(aString) - @contents = aString - @readPointer = 0 - end - - def read(charsToRead) - retVal=@contents[@readPointer, charsToRead] - @readPointer+=charsToRead - return retVal - end - - def produceInput - read(100) - end - - def inputFinished? - @contents[@readPointer] == nil - end - end - - def setup - @io = TestAbstractInputStream.new(TEST_STRING) - end - - def test_gets - assert_equals(TEST_LINES[0], @io.gets) - assert_equals(TEST_LINES[1], @io.gets) - assert_equals(TEST_LINES[2], @io.gets) - assert_equals(nil, @io.gets) - end - - def test_getsMultiCharSeperator - assert_equals("Hell", @io.gets("ll")) - assert_equals("o world#{$/}this is the second l", @io.gets("d l")) - end - - def test_each_line - lineNumber=0 - @io.each_line { - |line| - assert_equals(TEST_LINES[lineNumber], line) - lineNumber+=1 - } - end - - def test_readlines - assert_equals(TEST_LINES, @io.readlines) - end - - def test_readline - test_gets - begin - @io.readline - fail "EOFError expected" - rescue EOFError - end - end -end - -class ZipEntryTest < RUNIT::TestCase - TEST_ZIPFILE = "someZipFile.zip" - TEST_COMMENT = "a comment" - TEST_COMPRESSED_SIZE = 1234 - TEST_CRC = 325324 - TEST_EXTRA = "Some data here" - TEST_COMPRESSIONMETHOD = ZipEntry::DEFLATED - TEST_NAME = "entry name" - TEST_SIZE = 8432 - TEST_ISDIRECTORY = false - - def test_constructorAndGetters - entry = ZipEntry.new(TEST_ZIPFILE, - TEST_NAME, - TEST_COMMENT, - TEST_EXTRA, - TEST_COMPRESSED_SIZE, - TEST_CRC, - TEST_COMPRESSIONMETHOD, - TEST_SIZE) - - assert_equals(TEST_COMMENT, entry.comment) - assert_equals(TEST_COMPRESSED_SIZE, entry.compressedSize) - assert_equals(TEST_CRC, entry.crc) - assert_equals(TEST_EXTRA, entry.extra) - assert_equals(TEST_COMPRESSIONMETHOD, entry.compressionMethod) - assert_equals(TEST_NAME, entry.name) - assert_equals(TEST_SIZE, entry.size) - assert_equals(TEST_ISDIRECTORY, entry.isDirectory) - end - - def test_equality - entry1 = ZipEntry.new("file.zip", "name", "isNotCompared", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry2 = ZipEntry.new("file.zip", "name", "isNotComparedXXX", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry3 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry4 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry5 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 1234, - ZipEntry::DEFLATED, 10000) - entry6 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::DEFLATED, 10000) - entry7 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::STORED, 10000) - entry8 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::STORED, 100000) - - assert_equals(entry1, entry1) - assert_equals(entry1, entry2) - - assert(entry2 != entry3) - assert(entry3 != entry4) - assert(entry4 != entry5) - assert(entry5 != entry6) - assert(entry6 != entry7) - assert(entry7 != entry8) - - assert(entry7 != "hello") - assert(entry7 != 12) - end -end - -module IOizeString - attr_reader :tell - - def read(count = nil) - @tell ||= 0 - count = size unless count - retVal = slice(@tell, count) - @tell += count - return retVal - end - - def seek(index, offset) - @tell ||= 0 - case offset - when IO::SEEK_END - newPos = size + index - when IO::SEEK_SET - newPos = index - when IO::SEEK_CUR - newPos = @tell + index - else - raise "Error in test method IOizeString::seek" - end - if (newPos < 0 || newPos >= size) - raise Errno::EINVAL - else - @tell=newPos - end - end - - def reset - @tell = 0 - end -end - -class ZipLocalEntryTest < RUNIT::TestCase - def test_readLocalEntryHeaderOfFirstTestZipEntry - File.open(TestZipFile::TEST_ZIP3.zipName) { - |file| - entry = ZipEntry.readLocalEntry(file) - - assert_equal("", entry.comment) - # Differs from windows and unix because of CR LF - # assert_equal(480, entry.compressedSize) - # assert_equal(0x2a27930f, entry.crc) - # extra field is 21 bytes long - # probably contains some unix attrutes or something - # disabled: assert_equal(nil, entry.extra) - assert_equal(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equal(TestZipFile::TEST_ZIP3.entryNames[0], entry.name) - assert_equal(File.size(TestZipFile::TEST_ZIP3.entryNames[0]), entry.size) - assert(! entry.isDirectory) - } - end - - def test_readLocalEntryFromNonZipFile - File.open("ziptest.rb") { - |file| - assert_equals(nil, ZipEntry.readLocalEntry(file)) - } - end - - def test_readLocalEntryFromTruncatedZipFile - zipFragment="" - File.open(TestZipFile::TEST_ZIP2.zipName) { |f| zipFragment = f.read(12) } # local header is at least 30 bytes - zipFragment.extend(IOizeString).reset - entry = ZipEntry.new - entry.readLocalEntry(zipFragment) - fail "ZipError expected" - rescue ZipError - end - - def test_writeEntry - entry = ZipEntry.new("file.zip", "entryName", "my little comment", - "thisIsSomeExtraInformation", 100, 987654, - ZipEntry::DEFLATED, 400) - writeToFile("localEntryHeader.bin", "centralEntryHeader.bin", entry) - entryReadLocal, entryReadCentral = readFromFile("localEntryHeader.bin", "centralEntryHeader.bin") - compareLocalEntryHeaders(entry, entryReadLocal) - compareCDirEntryHeaders(entry, entryReadCentral) - end - - private - def compareLocalEntryHeaders(entry1, entry2) - assert_equals(entry1.compressedSize , entry2.compressedSize) - assert_equals(entry1.crc , entry2.crc) - assert_equals(entry1.extra , entry2.extra) - assert_equals(entry1.compressionMethod, entry2.compressionMethod) - assert_equals(entry1.name , entry2.name) - assert_equals(entry1.size , entry2.size) - assert_equals(entry1.localHeaderOffset, entry2.localHeaderOffset) - end - - def compareCDirEntryHeaders(entry1, entry2) - compareLocalEntryHeaders(entry1, entry2) - assert_equals(entry1.comment, entry2.comment) - end - - def writeToFile(localFileName, centralFileName, entry) - File.open(localFileName, "wb") { |f| entry.writeLocalEntry(f) } - File.open(centralFileName, "wb") { |f| entry.writeCDirEntry(f) } - end - - def readFromFile(localFileName, centralFileName) - localEntry = nil - cdirEntry = nil - File.open(localFileName, "rb") { |f| localEntry = ZipEntry.readLocalEntry(f) } - File.open(centralFileName, "rb") { |f| cdirEntry = ZipEntry.readCDirEntry(f) } - return [localEntry, cdirEntry] - end -end - - -module DecompressorTests - # expects @refText and @decompressor - - def test_readEverything - assert_equals(@refText, @decompressor.read) - end - - def test_readInChunks - chunkSize = 5 - while (decompressedChunk = @decompressor.read(chunkSize)) - assert_equals(@refText.slice!(0, chunkSize), decompressedChunk) - end - assert_equals(0, @refText.size) - end -end - -class InflaterTest < RUNIT::TestCase - include DecompressorTests - - def setup - @file = File.new("file1.txt.deflatedData", "rb") - @refText="" - File.open("file1.txt") { |f| @refText = f.read } - @decompressor = Inflater.new(@file) - end - - def teardown - @file.close - end -end - - -class PassThruDecompressorTest < RUNIT::TestCase - include DecompressorTests - TEST_FILE="file1.txt" - def setup - @file = File.new(TEST_FILE) - @refText="" - File.open(TEST_FILE) { |f| @refText = f.read } - @decompressor = PassThruDecompressor.new(@file, File.size(TEST_FILE)) - end - - def teardown - @file.close - end -end - - -module AssertEntry - def assertNextEntry(filename, zis) - assertEntry(filename, zis, zis.getNextEntry.name) - end - - def assertEntry(filename, zis, entryName) - assert_equals(filename, entryName) - assertEntryContentsForStream(filename, zis, entryName) - end - - def assertEntryContentsForStream(filename, zis, entryName) - File.open(filename, "rb") { - |file| - expected = file.read - actual = zis.read - if (expected != actual) - if (expected.length > 400 || actual.length > 400) - zipEntryFilename=entryName+".zipEntry" - File.open(zipEntryFilename, "wb") { |file| file << actual } - fail("File '#{filename}' is different from '#{zipEntryFilename}'") - else - assert_equals(expected, actual) - end - end - } - end - - def AssertEntry.assertContents(filename, aString) - fileContents = "" - File.open(filename, "rb") { |f| fileContents = f.read } - if (fileContents != aString) - if (expected.length > 400 || actual.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_equals(expected, actual) - end - end - end - - def assertStreamContents(zis, testZipFile) - assert(zis != nil) - testZipFile.entryNames.each { - |entryName| - assertNextEntry(entryName, zis) - } - assert_equals(nil, zis.getNextEntry) - end - - def assertTestZipContents(testZipFile) - ZipInputStream.open(testZipFile.zipName) { - |zis| - assertStreamContents(zis, testZipFile) - } - end - - def assertEntryContents(zipFile, entryName, filename = entryName.to_s) - zis = zipFile.getInputStream(entryName) - assertEntryContentsForStream(filename, zis, entryName) - ensure - zis.close if zis - end -end - - - -class ZipInputStreamTest < RUNIT::TestCase - include AssertEntry - - def test_new - zis = ZipInputStream.new(TestZipFile::TEST_ZIP2.zipName) - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - zis.close - end - - def test_openWithBlock - ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) { - |zis| - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - } - end - - def test_openWithoutBlock - zis = ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - end - - def test_incompleteReads - ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) { - |zis| - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[0], entry.name) - assert zis.gets.length > 0 - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[1], entry.name) - assert_equals(0, entry.size) - assert_equals(nil, zis.gets) - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[2], entry.name) - assert zis.gets.length > 0 - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[3], entry.name) - assert zis.gets.length > 0 - } - end - -end - -class TestFiles - RANDOM_ASCII_FILE1 = "randomAscii1.txt" - RANDOM_ASCII_FILE2 = "randomAscii2.txt" - RANDOM_ASCII_FILE3 = "randomAscii3.txt" - RANDOM_BINARY_FILE1 = "randomBinary1.bin" - RANDOM_BINARY_FILE2 = "randomBinary2.bin" - - EMPTY_TEST_DIR = "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 ] - TEST_FILES = [ ASCII_TEST_FILES, BINARY_TEST_FILES, EMPTY_TEST_DIR ].flatten! - - def TestFiles.createTestFiles(recreate) - if (recreate || - ! (TEST_FILES.inject(true) { |accum, element| accum && File.exists?(element) })) - - ASCII_TEST_FILES.each_with_index { - |filename, index| - createRandomAscii(filename, 1E4 * (index+1)) - } - - BINARY_TEST_FILES.each_with_index { - |filename, index| - createRandomBinary(filename, 1E4 * (index+1)) - } - - ensureDir(EMPTY_TEST_DIR) - end - end - - private - def TestFiles.createRandomAscii(filename, size) - File.open(filename, "wb") { - |file| - while (file.tell < size) - file << rand - end - } - end - - def TestFiles.createRandomBinary(filename, size) - File.open(filename, "wb") { - |file| - while (file.tell < size) - file << rand.to_a.pack("V") - end - } - end - - def TestFiles.ensureDir(name) - if File.exists?(name) - return if File.stat(name).directory? - File.delete(name) - end - Dir.mkdir(name) - end - -end - -# For representation and creation of -# test data -class TestZipFile - attr_accessor :zipName, :entryNames, :comment - - def initialize(zipName, entryNames, comment = "") - @zipName=zipName - @entryNames=entryNames - @comment = comment - end - - def TestZipFile.createTestZips(recreate) - files = Dir.entries(".") - if (recreate || - ! (files.index(TEST_ZIP1.zipName) && - files.index(TEST_ZIP2.zipName) && - files.index(TEST_ZIP3.zipName) && - files.index(TEST_ZIP4.zipName) && - files.index("empty.txt") && - files.index("short.txt") && - files.index("longAscii.txt") && - files.index("longBinary.bin") )) - raise "failed to create test zip '#{TEST_ZIP1.zipName}'" unless - system("zip #{TEST_ZIP1.zipName} ziptest.rb") - raise "failed to remove entry from '#{TEST_ZIP1.zipName}'" unless - system("zip #{TEST_ZIP1.zipName} -d ziptest.rb") - - File.open("empty.txt", "w") {} - - File.open("short.txt", "w") { |file| file << "ABCDEF" } - ziptestTxt="" - File.open("ziptest.rb") { |file| ziptestTxt=file.read } - File.open("longAscii.txt", "w") { - |file| - while (file.tell < 1E5) - file << ziptestTxt - end - } - - testBinaryPattern="" - File.open("empty.zip") { |file| testBinaryPattern=file.read } - testBinaryPattern *= 4 - - File.open("longBinary.bin", "wb") { - |file| - while (file.tell < 3E5) - file << testBinaryPattern << rand - end - } - raise "failed to create test zip '#{TEST_ZIP2.zipName}'" unless - system("zip #{TEST_ZIP2.zipName} #{TEST_ZIP2.entryNames.join(' ')}") - - # without bash system interprets everything after echo as parameters to - # echo including | zip -z ... - raise "failed to add comment to test zip '#{TEST_ZIP2.zipName}'" unless - system("bash -c \"echo #{TEST_ZIP2.comment} | zip -z #{TEST_ZIP2.zipName}\"") - - raise "failed to create test zip '#{TEST_ZIP3.zipName}'" unless - system("zip #{TEST_ZIP3.zipName} #{TEST_ZIP3.entryNames.join(' ')}") - - raise "failed to create test zip '#{TEST_ZIP4.zipName}'" unless - system("zip #{TEST_ZIP4.zipName} #{TEST_ZIP4.entryNames.join(' ')}") - end - rescue - raise $!.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." - end - - TEST_ZIP1 = TestZipFile.new("empty.zip", []) - TEST_ZIP2 = TestZipFile.new("4entry.zip", %w{ longAscii.txt empty.txt short.txt longBinary.bin}, - "my zip comment") - TEST_ZIP3 = TestZipFile.new("test1.zip", %w{ file1.txt }) - TEST_ZIP4 = TestZipFile.new("zipWithDir.zip", [ "file1.txt", - TestFiles::EMPTY_TEST_DIR]) -end - - -class AbstractOutputStreamTest < RUNIT::TestCase - class TestOutputStream - include AbstractOutputStream - - attr_accessor :buffer - - def initialize - @buffer = "" - end - - def << (data) - @buffer << data - self - end - end - - def setup - @outputStream = TestOutputStream.new - - @origCommaSep = $, - @origOutputSep = $\ - end - - def teardown - $, = @origCommaSep - $\ = @origOutputSep - end - - def test_write - count = @outputStream.write("a little string") - assert_equals("a little string", @outputStream.buffer) - assert_equals("a little string".length, count) - - count = @outputStream.write(". a little more") - assert_equals("a little string. a little more", @outputStream.buffer) - assert_equals(". a little more".length, count) - end - - def test_print - $\ = nil # record separator set to nil - @outputStream.print("hello") - assert_equals("hello", @outputStream.buffer) - - @outputStream.print(" world.") - assert_equals("hello world.", @outputStream.buffer) - - @outputStream.print(" You ok ", "out ", "there?") - assert_equals("hello world. You ok out there?", @outputStream.buffer) - - $\ = "\n" - @outputStream.print - assert_equals("hello world. You ok out there?\n", @outputStream.buffer) - - @outputStream.print("I sure hope so!") - assert_equals("hello world. You ok out there?\nI sure hope so!\n", @outputStream.buffer) - - $, = "X" - @outputStream.buffer = "" - @outputStream.print("monkey", "duck", "zebra") - assert_equals("monkeyXduckXzebra\n", @outputStream.buffer) - - $\ = nil - @outputStream.buffer = "" - @outputStream.print(20) - assert_equals("20", @outputStream.buffer) - end - - def test_printf - @outputStream.printf("%d %04x", 123, 123) - assert_equals("123 007b", @outputStream.buffer) - end - - def test_putc - @outputStream.putc("A") - assert_equals("A", @outputStream.buffer) - @outputStream.putc(65) - assert_equals("AA", @outputStream.buffer) - end - - def test_puts - @outputStream.puts - assert_equals("\n", @outputStream.buffer) - - @outputStream.puts("hello", "world") - assert_equals("\nhello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts("hello\n", "world\n") - assert_equals("hello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(["hello\n", "world\n"]) - assert_equals("hello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(["hello\n", "world\n"], "bingo") - assert_equals("hello\nworld\nbingo\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(16, 20, 50, "hello") - assert_equals("16\n20\n50\nhello\n", @outputStream.buffer) - end -end - - -module CrcTest - def runCrcTest(compressorClass) - str = "Here's a nice little text to compute the crc for! Ho hum, it is nice nice nice nice indeed." - fakeOut = AbstractOutputStreamTest::TestOutputStream.new - - deflater = compressorClass.new(fakeOut) - deflater << str - assert_equals(0x919920fc, deflater.crc) - end -end - - - -class PassThruCompressorTest < RUNIT::TestCase - include CrcTest - - def test_size - File.open("dummy.txt", "wb") { - |file| - compressor = PassThruCompressor.new(file) - - assert_equals(0, compressor.size) - - t1 = "hello world" - t2 = "" - t3 = "bingo" - - compressor << t1 - assert_equals(compressor.size, t1.size) - - compressor << t2 - assert_equals(compressor.size, t1.size + t2.size) - - compressor << t3 - assert_equals(compressor.size, t1.size + t2.size + t3.size) - } - end - - def test_crc - runCrcTest(PassThruCompressor) - end -end - -class DeflaterTest < RUNIT::TestCase - include CrcTest - - def test_outputOperator - txt = loadFile("ziptest.rb") - deflate(txt, "deflatertest.bin") - inflatedTxt = inflate("deflatertest.bin") - assert_equals(txt, inflatedTxt) - end - - private - def loadFile(fileName) - txt = nil - File.open(fileName, "rb") { |f| txt = f.read } - end - - def deflate(data, fileName) - File.open(fileName, "wb") { - |file| - deflater = Deflater.new(file) - deflater << data - deflater.finish - assert_equals(deflater.size, data.size) - file << "trailing data for zlib with -MAX_WBITS" - } - end - - def inflate(fileName) - txt = nil - File.open(fileName, "rb") { - |file| - inflater = Inflater.new(file) - txt = inflater.read - } - end - - def test_crc - runCrcTest(Deflater) - end -end - -class ZipOutputStreamTest < RUNIT::TestCase - include AssertEntry - - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zipName = "output.zip" - - def test_new - zos = ZipOutputStream.new(TEST_ZIP.zipName) - zos.comment = TEST_ZIP.comment - writeTestZip(zos) - zos.close - assertTestZipContents(TEST_ZIP) - end - - def test_open - ZipOutputStream.open(TEST_ZIP.zipName) { - |zos| - zos.comment = TEST_ZIP.comment - writeTestZip(zos) - } - assertTestZipContents(TEST_ZIP) - end - - def test_writingToClosedStream - assertIOErrorInClosedStream { |zos| zos << "hello world" } - assertIOErrorInClosedStream { |zos| zos.puts "hello world" } - assertIOErrorInClosedStream { |zos| zos.write "hello world" } - end - - def test_cannotOpenFile - name = TestFiles::EMPTY_TEST_DIR - begin - zos = ZipOutputStream.open(name) - rescue Exception - 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: #{$!.type}") - end - end - - def assertIOErrorInClosedStream - assert_exception(IOError) { - zos = ZipOutputStream.new("test_putOnClosedStream.zip") - zos.close - yield zos - } - end - - def writeTestZip(zos) - TEST_ZIP.entryNames.each { - |entryName| - zos.putNextEntry(entryName) - File.open(entryName, "rb") { |f| zos.write(f.read) } - } - end -end - - - -module Enumerable - def compareEnumerables(otherEnumerable) - otherAsArray = otherEnumerable.to_a - index=0 - each_with_index { - |element, index| - return false unless yield(element, otherAsArray[index]) - } - return index+1 == otherAsArray.size - end -end - - -class ZipCentralDirectoryEntryTest < RUNIT::TestCase - - def test_readFromStream - File.open("testDirectory.bin", "rb") { - |file| - entry = ZipEntry.readCDirEntry(file) - - assert_equals("longAscii.txt", entry.name) - assert_equals(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equals(106490, entry.size) - assert_equals(3784, entry.compressedSize) - assert_equals(0xfcd1799c, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("empty.txt", entry.name) - assert_equals(ZipEntry::STORED, entry.compressionMethod) - assert_equals(0, entry.size) - assert_equals(0, entry.compressedSize) - assert_equals(0x0, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("short.txt", entry.name) - assert_equals(ZipEntry::STORED, entry.compressionMethod) - assert_equals(6, entry.size) - assert_equals(6, entry.compressedSize) - assert_equals(0xbb76fe69, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("longBinary.bin", entry.name) - assert_equals(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equals(1000024, entry.size) - assert_equals(70847, entry.compressedSize) - assert_equals(0x10da7d59, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals(nil, entry) -# Fields that are not check by this test: -# version made by 2 bytes -# version needed to extract 2 bytes -# general purpose bit flag 2 bytes -# last mod file time 2 bytes -# last mod file date 2 bytes -# compressed size 4 bytes -# uncompressed size 4 bytes -# disk number start 2 bytes -# internal file attributes 2 bytes -# external file attributes 4 bytes -# relative offset of local header 4 bytes - -# file name (variable size) -# extra field (variable size) -# file comment (variable size) - - } - end - - def test_ReadEntryFromTruncatedZipFile - fragment="" - File.open("testDirectory.bin") { |f| fragment = f.read(12) } # cdir entry header is at least 46 bytes - fragment.extend(IOizeString) - entry = ZipEntry.new - entry.readCDirEntry(fragment) - fail "ZipError expected" - rescue ZipError - end - -end - -class ZipCentralDirectoryTest < RUNIT::TestCase - - def test_readFromStream - File.open(TestZipFile::TEST_ZIP2.zipName, "rb") { - |zipFile| - cdir = ZipCentralDirectory.readFromStream(zipFile) - - assert_equals(TestZipFile::TEST_ZIP2.entryNames.size, cdir.size) - assert(cdir.compareEnumerables(TestZipFile::TEST_ZIP2.entryNames) { - |cdirEntry, testEntryName| - cdirEntry.name == testEntryName - }) - assert_equals(TestZipFile::TEST_ZIP2.comment, cdir.comment) - } - end - - def test_readFromInvalidStream - File.open("ziptest.rb", "rb") { - |zipFile| - cdir = ZipCentralDirectory.new - cdir.readFromStream(zipFile) - } - fail "ZipError expected!" - rescue ZipError - end - - def test_ReadFromTruncatedZipFile - fragment="" - File.open("testDirectory.bin") { |f| fragment = f.read } - fragment.slice!(12) # removed part of first cdir entry. eocd structure still complete - fragment.extend(IOizeString) - entry = ZipCentralDirectory.new - entry.readFromStream(fragment) - fail "ZipError expected" - rescue ZipError - end - - def test_writeToStream - entries = [ ZipEntry.new("file.zip", "flimse", "myComment", "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt", "Has a comment too") ] - cdir = ZipCentralDirectory.new(entries, "my zip comment") - File.open("cdirtest.bin", "wb") { |f| cdir.writeToStream(f) } - cdirReadback = ZipCentralDirectory.new - File.open("cdirtest.bin", "rb") { |f| cdirReadback.readFromStream(f) } - - assert_equals(cdir.entries, cdirReadback.entries) - end - - def test_equality - cdir1 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "my zip comment") - cdir2 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "my zip comment") - cdir3 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "comment?") - cdir4 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "comment?") - assert_equals(cdir1, cdir1) - assert_equals(cdir1, cdir2) - - assert(cdir1 != cdir3) - assert(cdir2 != cdir3) - assert(cdir2 != cdir3) - assert(cdir3 != cdir4) - - assert(cdir3 != "hello") - end -end - - -class BasicZipFileTest < RUNIT::TestCase - include AssertEntry - - def setup - @zipFile = ZipFile.new(TestZipFile::TEST_ZIP2.zipName) - @testEntryNameIndex=0 - end - - def nextTestEntryName - retVal=TestZipFile::TEST_ZIP2.entryNames[@testEntryNameIndex] - @testEntryNameIndex+=1 - return retVal - end - - def test_entries - assert_equals(TestZipFile::TEST_ZIP2.entryNames, @zipFile.entries.map {|e| e.name} ) - end - - def test_each - @zipFile.each { - |entry| - assert_equals(nextTestEntryName, entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_foreach - ZipFile.foreach(TestZipFile::TEST_ZIP2.zipName) { - |entry| - assert_equals(nextTestEntryName, entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_getInputStream - @zipFile.each { - |entry| - assertEntry(nextTestEntryName, @zipFile.getInputStream(entry), - entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_getInputStreamBlock - fileAndEntryName = @zipFile.entries.first.name - @zipFile.getInputStream(fileAndEntryName) { - |zis| - assertEntryContentsForStream(fileAndEntryName, - zis, - fileAndEntryName) - } - end -end - -class CommonZipFileFixture < RUNIT::TestCase - include AssertEntry - - EMPTY_FILENAME = "emptyZipFile.zip" - - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zipName = "4entry_copy.zip" - - def setup - File.delete(EMPTY_FILENAME) if File.exists?(EMPTY_FILENAME) - File.copy(TestZipFile::TEST_ZIP2.zipName, TEST_ZIP.zipName) - end -end - -class ZipFileTest < CommonZipFileFixture - - def test_createFromScratch - comment = "a short comment" - - zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) - zf.comment = comment - zf.close - - zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equals(comment, zfRead.comment) - assert_equals(0, zfRead.entries.length) - end - - def test_add - srcFile = "ziptest.rb" - entryName = "newEntryName.rb" - assert(File.exists? srcFile) - zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) - zf.add(entryName, srcFile) - zf.close - - zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equals("", zfRead.comment) - assert_equals(1, zfRead.entries.length) - assert_equals(entryName, zfRead.entries.first.name) - AssertEntry.assertContents(srcFile, - zfRead.getInputStream(entryName) { |zis| zis.read }) - end - - def test_addExistingEntryName - assert_exception(ZipEntryExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.add(zf.entries.first.name, "ziptest.rb") - } - } - end - - def test_addExistingEntryNameReplace - gotCalled = false - replacedEntry = nil - ZipFile.open(TEST_ZIP.zipName) { - |zf| - replacedEntry = zf.entries.first.name - zf.add(replacedEntry, "ziptest.rb") { gotCalled = true; true } - } - assert(gotCalled) - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assertContains(zf, replacedEntry, "ziptest.rb") - } - end - - def test_addDirectory - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.add(TestFiles::EMPTY_TEST_DIR, TestFiles::EMPTY_TEST_DIR) - } - ZipFile.open(TEST_ZIP.zipName) { - |zf| - dirEntry = zf.entries.detect { |e| e.name == TestFiles::EMPTY_TEST_DIR+"/" } - assert(dirEntry.isDirectory) - } - end - - def test_remove - entryToRemove, *remainingEntries = TEST_ZIP.entryNames - - File.copy(TestZipFile::TEST_ZIP2.zipName, TEST_ZIP.zipName) - - zf = ZipFile.new(TEST_ZIP.zipName) - assert(zf.entries.map { |e| e.name }.include?(entryToRemove)) - zf.remove(entryToRemove) - assert(! zf.entries.map { |e| e.name }.include?(entryToRemove)) - assert_equals(zf.entries.map {|x| x.name }.sort, remainingEntries.sort) - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(! zfRead.entries.map { |e| e.name }.include?(entryToRemove)) - assert_equals(zfRead.entries.map {|x| x.name }.sort, remainingEntries.sort) - zfRead.close - end - - - def test_rename - entryToRename, *remainingEntries = TEST_ZIP.entryNames - - zf = ZipFile.new(TEST_ZIP.zipName) - assert(zf.entries.map { |e| e.name }.include? entryToRename) - - newName = "changed name" - assert(! zf.entries.map { |e| e.name }.include?(newName)) - - zf.rename(entryToRename, newName) - assert(zf.entries.map { |e| e.name }.include? newName) - - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(zfRead.entries.map { |e| e.name }.include? newName) - zfRead.close - end - - def test_renameToExistingEntry - oldEntries = nil - ZipFile.open(TEST_ZIP.zipName) { |zf| oldEntries = zf.entries } - - assert_exception(ZipEntryExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.rename(zf.entries[0], zf.entries[1].name) - } - } - - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_equals(oldEntries.map{ |e| e.name }, zf.entries.map{ |e| e.name }) - } - end - - def test_renameToExistingEntryOverwrite - oldEntries = nil - ZipFile.open(TEST_ZIP.zipName) { |zf| oldEntries = zf.entries } - - gotCalled = false - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.rename(zf.entries[0], zf.entries[1].name) { gotCalled = true; true } - } - - assert(gotCalled) - oldEntries.delete_at(0) - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_equals(oldEntries.map{ |e| e.name }, - zf.entries.map{ |e| e.name }) - } - end - - def test_renameNonEntry - nonEntry = "bogusEntry" - targetEntry = "targetEntryName" - zf = ZipFile.new(TEST_ZIP.zipName) - assert(! zf.entries.include?(nonEntry)) - assert_exception(ZipNoSuchEntryError) { - zf.rename(nonEntry, targetEntry) - } - zf.commit - assert(! zf.entries.include?(targetEntry)) - ensure - zf.close - end - - def test_renameEntryToExistingEntry - entry1, entry2, *remaining = TEST_ZIP.entryNames - zf = ZipFile.new(TEST_ZIP.zipName) - assert_exception(ZipEntryExistsError) { - zf.rename(entry1, entry2) - } - ensure - zf.close - end - - def test_replace - unchangedEntries = TEST_ZIP.entryNames.dup - entryToReplace = unchangedEntries.delete_at(2) - newEntrySrcFilename = "ziptest.rb" - - zf = ZipFile.new(TEST_ZIP.zipName) - zf.replace(entryToReplace, newEntrySrcFilename) - - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - AssertEntry::assertContents(newEntrySrcFilename, - zfRead.getInputStream(entryToReplace) { |is| is.read }) - zfRead.close - end - - def test_replaceNonEntry - entryToReplace = "nonExistingEntryname" - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_exception(ZipNoSuchEntryError) { - zf.replace(entryToReplace, "ziptest.rb") - } - } - end - - def test_commit - newName = "renamedFirst" - zf = ZipFile.new(TEST_ZIP.zipName) - oldName = zf.entries.first - zf.rename(oldName, newName) - zf.commit - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(zfRead.entries.detect { |e| e.name == newName } != nil) - assert(zfRead.entries.detect { |e| e.name == oldName } == nil) - zfRead.close - - zf.close - end - - # This test tests that after commit, you - # can delete the file you used to add the entry to the zip file - # with - def test_commitUseZipEntry - File.copy(TestFiles::RANDOM_ASCII_FILE1, "okToDelete.txt") - zf = ZipFile.open(TEST_ZIP.zipName) - zf.add("okToDelete.txt", "okToDelete.txt") - assertContains(zf, "okToDelete.txt") - zf.commit - File.move("okToDelete.txt", "okToDeleteMoved.txt") - assertContains(zf, "okToDelete.txt", "okToDeleteMoved.txt") - end - -# def test_close -# zf = ZipFile.new(TEST_ZIP.zipName) -# zf.close -# assert_exception(IOError) { -# zf.extract(TEST_ZIP.entryNames.first, "hullubullu") -# } -# end - - def test_compound1 - renamedName = "renamedName" - originalEntries = [] - begin - zf = ZipFile.new(TEST_ZIP.zipName) - originalEntries = zf.entries.dup - - assertNotContains(zf, TestFiles::RANDOM_ASCII_FILE1) - zf.add(TestFiles::RANDOM_ASCII_FILE1, - TestFiles::RANDOM_ASCII_FILE1) - assertContains(zf, TestFiles::RANDOM_ASCII_FILE1) - - zf.rename(zf.entries[0], renamedName) - assertContains(zf, renamedName) - - TestFiles::BINARY_TEST_FILES.each { - |filename| - zf.add(filename, filename) - assertContains(zf, filename) - } - - assertContains(zf, originalEntries.last.to_s) - zf.remove(originalEntries.last.to_s) - assertNotContains(zf, originalEntries.last.to_s) - - ensure - zf.close - end - begin - zfRead = ZipFile.new(TEST_ZIP.zipName) - assertContains(zfRead, TestFiles::RANDOM_ASCII_FILE1) - assertContains(zfRead, renamedName) - TestFiles::BINARY_TEST_FILES.each { - |filename| - assertContains(zfRead, filename) - } - assertNotContains(zfRead, originalEntries.last.to_s) - ensure - zfRead.close - end - end - - def test_compound2 - begin - zf = ZipFile.new(TEST_ZIP.zipName) - originalEntries = zf.entries.dup - - originalEntries.each { - |entry| - zf.remove(entry) - assertNotContains(zf, entry) - } - assert(zf.entries.empty?) - - TestFiles::ASCII_TEST_FILES.each { - |filename| - zf.add(filename, filename) - assertContains(zf, filename) - } - assert_equals(zf.entries.map { |e| e.name }, TestFiles::ASCII_TEST_FILES) - - zf.rename(TestFiles::ASCII_TEST_FILES[0], "newName") - assertNotContains(zf, TestFiles::ASCII_TEST_FILES[0]) - assertContains(zf, "newName") - ensure - zf.close - end - begin - zfRead = ZipFile.new(TEST_ZIP.zipName) - asciiTestFiles = TestFiles::ASCII_TEST_FILES.dup - asciiTestFiles.shift - asciiTestFiles.each { - |filename| - assertContains(zf, filename) - } - - assertContains(zf, "newName") - ensure - zfRead.close - end - end - - private - def assertContains(zf, entryName, filename = entryName) - assert(zf.entries.detect { |e| e.name == entryName} != nil, "entry #{entryName} not in #{zf.entries.join(', ')} in zip file #{zf}") - assertEntryContents(zf, entryName, filename) if File.exists?(filename) - end - - def assertNotContains(zf, entryName) - assert(zf.entries.detect { |e| e.name == entryName} == nil, "entry #{entryName} in #{zf.entries.join(', ')} in zip file #{zf}") - end -end - -class ZipFileExtractTest < CommonZipFileFixture - EXTRACTED_FILENAME = "extEntry" - ENTRY_TO_EXTRACT, *REMAINING_ENTRIES = TEST_ZIP.entryNames.reverse - - def setup - super - File.delete(EXTRACTED_FILENAME) if File.exists?(EXTRACTED_FILENAME) - end - - def test_extract - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(ENTRY_TO_EXTRACT, EXTRACTED_FILENAME) - - assert(File.exists? EXTRACTED_FILENAME) - AssertEntry::assertContents(EXTRACTED_FILENAME, - zf.getInputStream(ENTRY_TO_EXTRACT) { |is| is.read }) - } - end - - def test_extractExists - writtenText = "written text" - File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - - assert_exception(ZipDestinationFileExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) - } - } - File.open(EXTRACTED_FILENAME, "r") { - |f| - assert_equals(writtenText, f.read) - } - end - - def test_extractExistsOverwrite - writtenText = "written text" - File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - - gotCalled = false - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) { gotCalled = true; true } - } - - assert(gotCalled) - File.open(EXTRACTED_FILENAME, "r") { - |f| - assert(writtenText != f.read) - } - end - - def test_extractNonEntry - zf = ZipFile.new(TEST_ZIP.zipName) - assert_exception(ZipNoSuchEntryError) { zf.extract("nonExistingEntry", "nonExistingEntry") } - ensure - zf.close if zf - end - - def test_extractNonEntry2 - outFile = "outfile" - assert_exception(ZipNoSuchEntryError) { - zf = ZipFile.new(TEST_ZIP.zipName) - nonEntry = "hotdog-diddelidoo" - assert(! zf.entries.include?(nonEntry)) - zf.extract(nonEntry, outFile) - zf.close - } - assert(! File.exists?(outFile)) - end - -end - -class ZipFileExtractDirectoryTest < CommonZipFileFixture - TEST_OUT_NAME = "emptyOutDir" - - def openZip(&aProc) - assert(aProc != nil) - ZipFile.open(TestZipFile::TEST_ZIP4.zipName, &aProc) - end - - def extractTestDir(&aProc) - openZip { - |zf| - zf.extract(TestFiles::EMPTY_TEST_DIR, TEST_OUT_NAME, &aProc) - } - end - - def setup - super - - Dir.rmdir(TEST_OUT_NAME) if File.directory? TEST_OUT_NAME - File.delete(TEST_OUT_NAME) if File.exists? TEST_OUT_NAME - end - - def test_extractDirectory - extractTestDir - assert(File.directory? TEST_OUT_NAME) - end - - def test_extractDirectoryExistsAsDir - Dir.mkdir TEST_OUT_NAME - extractTestDir - assert(File.directory? TEST_OUT_NAME) - end - - def test_extractDirectoryExistsAsFile - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - assert_exception(ZipDestinationFileExistsError) { extractTestDir } - end - - def test_extractDirectoryExistsAsFileOverwrite - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - gotCalled = false - extractTestDir { - |entry, destPath| - gotCalled = true - assert_equals(TEST_OUT_NAME, destPath) - assert(entry.isDirectory) - true - } - assert(gotCalled) - assert(File.directory? TEST_OUT_NAME) - end -end - - -TestFiles::createTestFiles(ARGV.index("recreate") != nil || - ARGV.index("recreateonly") != nil) -TestZipFile::createTestZips(ARGV.index("recreate") != nil || - ARGV.index("recreateonly") != nil) -exit if ARGV.index("recreateonly") != nil - -#require 'runit/cui/testrunner' -#RUNIT::CUI::TestRunner.run(ZipFileTest.suite) - -# Copyright (C) 2002 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/test/extra_field_test.rb b/test/extra_field_test.rb new file mode 100644 index 00000000..fa6e212d --- /dev/null +++ b/test/extra_field_test.rb @@ -0,0 +1,76 @@ +require 'test_helper' + +class ZipExtraFieldTest < MiniTest::Test + def test_new + extra_pure = ::Zip::ExtraField.new('') + extra_withstr = ::Zip::ExtraField.new('foo') + assert_instance_of(::Zip::ExtraField, extra_pure) + assert_instance_of(::Zip::ExtraField, extra_withstr) + end + + def test_unknownfield + extra = ::Zip::ExtraField.new('foo') + assert_equal(extra['Unknown'], 'foo') + extra.merge('a') + assert_equal(extra['Unknown'], 'fooa') + extra.merge('barbaz') + assert_equal(extra.to_s, 'fooabarbaz') + 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) + assert(extra.member?('NTFS')) + t = ::Zip::DOSTime.at(1_410_496_497.405178) + assert_equal(t, extra['NTFS'].mtime) + assert_equal(t, extra['NTFS'].atime) + assert_equal(t, extra['NTFS'].ctime) + end + + def test_merge + str = "UT\x5\0\x3\250$\r@Ux\0\0" + extra1 = ::Zip::ExtraField.new('') + extra2 = ::Zip::ExtraField.new(str) + assert(!extra1.member?('UniversalTime')) + assert(extra2.member?('UniversalTime')) + extra1.merge(str) + assert_equal(extra1['UniversalTime'].mtime, extra2['UniversalTime'].mtime) + end + + def test_length + str = "UT\x5\0\x3\250$\r@Ux\0\0Te\0\0testit" + extra = ::Zip::ExtraField.new(str) + assert_equal(extra.local_size, extra.to_local_bin.size) + assert_equal(extra.c_dir_size, extra.to_c_dir_bin.size) + extra.merge('foo') + assert_equal(extra.local_size, extra.to_local_bin.size) + assert_equal(extra.c_dir_size, extra.to_c_dir_bin.size) + end + + def test_to_s + str = "UT\x5\0\x3\250$\r@Ux\0\0Te\0\0testit" + 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) + end + + def test_equality + str = "UT\x5\0\x3\250$\r@" + extra1 = ::Zip::ExtraField.new(str) + extra2 = ::Zip::ExtraField.new(str) + extra3 = ::Zip::ExtraField.new(str) + assert_equal(extra1, extra2) + + extra2['UniversalTime'].mtime = ::Zip::DOSTime.now + assert(extra1 != extra2) + + extra3.create('IUnix') + assert(extra1 != extra3) + + extra1.create('IUnix') + assert_equal(extra1, extra3) + end +end diff --git a/test/file_extract_directory_test.rb b/test/file_extract_directory_test.rb new file mode 100644 index 00000000..f14f7870 --- /dev/null +++ b/test/file_extract_directory_test.rb @@ -0,0 +1,54 @@ +require 'test_helper' + +class ZipFileExtractDirectoryTest < MiniTest::Test + include CommonZipFileFixture + + TEST_OUT_NAME = 'test/data/generated/emptyOutDir' + + def open_zip(&aProc) + assert(!aProc.nil?) + ::Zip::File.open(TestZipFile::TEST_ZIP4.zip_name, &aProc) + end + + def extract_test_dir(&aProc) + open_zip do |zf| + zf.extract(TestFiles::EMPTY_TEST_DIR, TEST_OUT_NAME, &aProc) + end + end + + 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 + end + + def test_extract_directory + extract_test_dir + assert(File.directory?(TEST_OUT_NAME)) + end + + def test_extract_directory_exists_as_dir + Dir.mkdir TEST_OUT_NAME + extract_test_dir + assert(File.directory?(TEST_OUT_NAME)) + end + + def test_extract_directory_exists_as_file + File.open(TEST_OUT_NAME, 'w') { |f| f.puts 'something' } + assert_raises(::Zip::DestinationFileExistsError) { extract_test_dir } + end + + 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| + gotCalled = true + assert_equal(TEST_OUT_NAME, destPath) + assert(entry.directory?) + true + end + assert(gotCalled) + assert(File.directory?(TEST_OUT_NAME)) + end +end diff --git a/test/file_extract_test.rb b/test/file_extract_test.rb new file mode 100644 index 00000000..6103aeae --- /dev/null +++ b/test/file_extract_test.rb @@ -0,0 +1,145 @@ +require 'test_helper' + +class ZipFileExtractTest < MiniTest::Test + include CommonZipFileFixture + EXTRACTED_FILENAME = 'test/data/generated/extEntry' + ENTRY_TO_EXTRACT, *REMAINING_ENTRIES = TEST_ZIP.entry_names.reverse + + def setup + super + ::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) + + assert(File.exist?(EXTRACTED_FILENAME)) + AssertEntry.assert_contents(EXTRACTED_FILENAME, + zf.get_input_stream(ENTRY_TO_EXTRACT) { |is| is.read }) + + ::File.unlink(EXTRACTED_FILENAME) + + entry = zf.get_entry(ENTRY_TO_EXTRACT) + entry.extract(EXTRACTED_FILENAME) + + assert(File.exist?(EXTRACTED_FILENAME)) + AssertEntry.assert_contents(EXTRACTED_FILENAME, + entry.get_input_stream { |is| is.read }) + end + end + + def test_extract_exists + writtenText = 'written text' + ::File.open(EXTRACTED_FILENAME, 'w') { |f| f.write(writtenText) } + + assert_raises(::Zip::DestinationFileExistsError) 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(writtenText, f.read) + end + end + + def test_extract_exists_overwrite + writtenText = 'written text' + ::File.open(EXTRACTED_FILENAME, 'w') { |f| f.write(writtenText) } + + gotCalledCorrectly = false + ::Zip::File.open(TEST_ZIP.zip_name) do |zf| + zf.extract(zf.entries.first, EXTRACTED_FILENAME) do |entry, extractLoc| + gotCalledCorrectly = zf.entries.first == entry && + extractLoc == EXTRACTED_FILENAME + true + end + end + + assert(gotCalledCorrectly) + ::File.open(EXTRACTED_FILENAME, 'r') do |f| + assert(writtenText != f.read) + end + end + + def test_extract_non_entry + zf = ::Zip::File.new(TEST_ZIP.zip_name) + assert_raises(Errno::ENOENT) { zf.extract('nonExistingEntry', 'nonExistingEntry') } + ensure + zf.close if zf + end + + def test_extract_non_entry_2 + outFile = '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) + zf.close + 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 diff --git a/test/file_permissions_test.rb b/test/file_permissions_test.rb new file mode 100644 index 00000000..4e4573a4 --- /dev/null +++ b/test/file_permissions_test.rb @@ -0,0 +1,65 @@ +require 'test_helper' + +class FilePermissionsTest < MiniTest::Test + ZIPNAME = File.join(File.dirname(__FILE__), 'umask.zip') + FILENAME = File.join(File.dirname(__FILE__), 'umask.txt') + + def teardown + ::File.unlink(ZIPNAME) + ::File.unlink(FILENAME) + end + + def test_current_umask + create_files + assert_matching_permissions FILENAME, ZIPNAME + end + + def test_umask_000 + set_umask(0o000) do + create_files + end + + assert_matching_permissions FILENAME, ZIPNAME + end + + def test_umask_066 + set_umask(0o066) do + create_files + end + + assert_matching_permissions FILENAME, ZIPNAME + end + + def test_umask_027 + set_umask(0o027) do + create_files + end + + assert_matching_permissions FILENAME, ZIPNAME + end + + def assert_matching_permissions(expected_file, actual_file) + assert_equal( + ::File.stat(expected_file).mode.to_s(8).rjust(4, '0'), + ::File.stat(actual_file).mode.to_s(8).rjust(4, '0') + ) + end + + def create_files + ::Zip::File.open(ZIPNAME, ::Zip::File::CREATE) do |zip| + zip.comment = 'test' + end + + ::File.open(FILENAME, 'w') do |file| + file << 'test' + end + end + + # If anything goes wrong, make sure the umask is restored. + def set_umask(umask) + saved_umask = ::File.umask(umask) + yield + ensure + ::File.umask(saved_umask) + end +end diff --git a/test/file_split_test.rb b/test/file_split_test.rb new file mode 100644 index 00000000..dfea837d --- /dev/null +++ b/test/file_split_test.rb @@ -0,0 +1,57 @@ +require 'test_helper' + +class ZipFileSplitTest < MiniTest::Test + TEST_ZIP = TestZipFile::TEST_ZIP2.clone + TEST_ZIP.zip_name = 'large_zip_file.zip' + EXTRACTED_FILENAME = 'test/data/generated/extEntrySplit' + UNSPLITTED_FILENAME = 'test/data/generated/unsplitted.zip' + ENTRY_TO_EXTRACT = TEST_ZIP.entry_names.first + + def setup + FileUtils.cp(TestZipFile::TEST_ZIP2.zip_name, TEST_ZIP.zip_name) + end + + def teardown + File.delete(TEST_ZIP.zip_name) + File.delete(UNSPLITTED_FILENAME) if File.exist?(UNSPLITTED_FILENAME) + + Dir["#{TEST_ZIP.zip_name}.*"].each do |zip_file_name| + File.delete(zip_file_name) if File.exist?(zip_file_name) + end + end + + def test_split_method_respond + assert_respond_to ::Zip::File, :split, 'Does not have split class method' + end + + 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 + File.open(UNSPLITTED_FILENAME, 'ab') do |file| + file << zip_file.read + end + end + end + + ::Zip::File.open(UNSPLITTED_FILENAME) do |zf| + zf.extract(ENTRY_TO_EXTRACT, EXTRACTED_FILENAME) + + assert(File.exist?(EXTRACTED_FILENAME)) + AssertEntry.assert_contents(EXTRACTED_FILENAME, + zf.get_input_stream(ENTRY_TO_EXTRACT) { |is| is.read }) + + File.unlink(EXTRACTED_FILENAME) + + entry = zf.get_entry(ENTRY_TO_EXTRACT) + entry.extract(EXTRACTED_FILENAME) + + assert(File.exist?(EXTRACTED_FILENAME)) + AssertEntry.assert_contents(EXTRACTED_FILENAME, + entry.get_input_stream { |is| is.read }) + end + end +end diff --git a/test/file_test.rb b/test/file_test.rb new file mode 100644 index 00000000..94ff769c --- /dev/null +++ b/test/file_test.rb @@ -0,0 +1,666 @@ +require 'test_helper' + +class ZipFileTest < MiniTest::Test + include CommonZipFileFixture + include ZipEntryData + + OK_DELETE_FILE = 'test/data/generated/okToDelete.txt' + OK_DELETE_MOVED_FILE = 'test/data/generated/okToDeleteMoved.txt' + + def teardown + ::Zip.write_zip64_support = false + end + + def test_create_from_scratch_to_buffer + comment = 'a short comment' + + buffer = ::Zip::File.add_buffer do |zf| + zf.get_output_stream('myFile') { |os| os.write 'myFile contains just this' } + zf.mkdir('dir1') + zf.comment = comment + end + + ::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) + end + + 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 + + zfRead = ::Zip::File.new(EMPTY_FILENAME) + assert_equal(comment, zfRead.comment) + assert_equal(2, zfRead.entries.length) + end + + def test_create_from_scratch_with_old_create_parameter + comment = 'a short comment' + + zf = ::Zip::File.new(EMPTY_FILENAME, 1) + zf.get_output_stream('myFile') { |os| os.write 'myFile contains just this' } + zf.mkdir('dir1') + zf.comment = comment + zf.close + + zfRead = ::Zip::File.new(EMPTY_FILENAME) + assert_equal(comment, zfRead.comment) + assert_equal(2, zfRead.entries.length) + end + + 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")) + end + end + + def test_get_output_stream + entryCount = 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' + end + assert_equal(entryCount + 1, zf.size) + assert_equal('Putting stuff in newEntry.txt', zf.read('newEntry.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('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) + 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.size) + assert_equal(custom_entry_args[6], entry.time) + + zf.get_output_stream('entry.bin') do |os| + os.write(::File.open('test/data/generated/5entry.zip', 'rb').read) + end + 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('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 + 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| + assert zf.entries.map { |e| e.name }.include?('zippedruby1.rb') + end + end + + def test_close_buffer_with_stringio + string_io = StringIO.new File.read('test/data/rubycode.zip') + zf = ::Zip::File.open_buffer string_io + assert_nil zf.close + end + + 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 + 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 + assert zf.entries.map { |e| e.name }.include?('zippedruby1.rb') + end + + def test_cleans_up_tempfiles_after_close + zf = ::Zip::File.new(EMPTY_FILENAME, ::Zip::File::CREATE) + zf.get_output_stream('myFile') do |os| + @tempfile_path = os.path + os.write 'myFile contains just this' + end + + assert_equal(true, File.exist?(@tempfile_path)) + + zf.close + + assert_equal(false, File.exist?(@tempfile_path)) + end + + def test_add + srcFile = 'test/data/file2.txt' + entryName = 'newEntryName.rb' + assert(::File.exist?(srcFile)) + zf = ::Zip::File.new(EMPTY_FILENAME, ::Zip::File::CREATE) + zf.add(entryName, srcFile) + 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) { |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(File.size(srcFile), 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) { |zis| zis.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) + zf.close + assert_equal(::File.stat(srcZip).mode, 0o100664) + end + + def test_add_existing_entry_name + 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 + end + + def test_add_existing_entry_name_replace + gotCalled = false + 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 } + end + assert(gotCalled) + ::Zip::File.open(TEST_ZIP.zip_name) do |zf| + assert_contains(zf, replacedEntry, 'test/data/file2.txt') + end + end + + 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?) + end + end + + def test_remove + entryToRemove, *remainingEntries = 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 { |e| e.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) + 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) + zfRead.close + end + + 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)) + + contents = zf.read(entryToRename) + newName = 'changed entry name' + assert(!zf.entries.map { |e| e.name }.include?(newName)) + + zf.rename(entryToRename, newName) + assert(zf.entries.map { |e| e.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_equal(contents, zfRead.read(newName)) + zfRead.close + end + + def test_rename_with_each + zf_name = 'test_rename_zip.zip' + ::File.unlink(zf_name) if ::File.exist?(zf_name) + arr = [] + arr_renamed = [] + ::Zip::File.open(zf_name, ::Zip::File::CREATE) do |zf| + zf.mkdir('test') + arr << 'test/' + arr_renamed << 'Ztest/' + %w[a b c d].each do |f| + zf.get_output_stream("test/#{f}") { |file| file.puts 'aaaa' } + arr << "test/#{f}" + arr_renamed << "Ztest/#{f}" + end + end + zf = ::Zip::File.open(zf_name) + assert_equal(zf.entries.map(&:name), arr) + zf.close + Zip::File.open(zf_name, 'wb') do |z| + z.each do |f| + z.rename(f, "Z#{f.name}") + end + end + zf = ::Zip::File.open(zf_name) + assert_equal(zf.entries.map(&:name), arr_renamed) + zf.close + ::File.unlink(zf_name) if ::File.exist?(zf_name) + end + + def test_rename_to_existing_entry + oldEntries = nil + ::Zip::File.open(TEST_ZIP.zip_name) { |zf| oldEntries = zf.entries } + + 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 + + ::Zip::File.open(TEST_ZIP.zip_name) do |zf| + assert_equal(oldEntries.sort.map { |e| e.name }, zf.entries.sort.map { |e| e.name }) + end + end + + def test_rename_to_existing_entry_overwrite + oldEntries = nil + ::Zip::File.open(TEST_ZIP.zip_name) { |zf| oldEntries = zf.entries } + + gotCalled = false + 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 } + end + + 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 }) + end + end + + def test_rename_non_entry + nonEntry = '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) } + zf.commit + assert(!zf.entries.include?(target_entry)) + ensure + zf.close + end + + 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) } + ensure + zf.close + end + + def test_replace + entryToReplace = TEST_ZIP.entry_names[2] + newEntrySrcFilename = 'test/data/file2.txt' + zf = ::Zip::File.new(TEST_ZIP.zip_name) + zf.replace(entryToReplace, newEntrySrcFilename) + + zf.close + zfRead = ::Zip::File.new(TEST_ZIP.zip_name) + AssertEntry.assert_contents(newEntrySrcFilename, + zfRead.get_input_stream(entryToReplace) { |is| is.read }) + AssertEntry.assert_contents(TEST_ZIP.entry_names[0], + zfRead.get_input_stream(TEST_ZIP.entry_names[0]) { |is| is.read }) + AssertEntry.assert_contents(TEST_ZIP.entry_names[1], + zfRead.get_input_stream(TEST_ZIP.entry_names[1]) { |is| is.read }) + AssertEntry.assert_contents(TEST_ZIP.entry_names[3], + zfRead.get_input_stream(TEST_ZIP.entry_names[3]) { |is| is.read }) + zfRead.close + end + + def test_replace_non_entry + entryToReplace = 'nonExistingEntryname' + ::Zip::File.open(TEST_ZIP.zip_name) do |zf| + assert_raises(Errno::ENOENT) { zf.replace(entryToReplace, 'test/data/file2.txt') } + end + end + + def test_commit + newName = 'renamedFirst' + zf = ::Zip::File.new(TEST_ZIP.zip_name) + oldName = zf.entries.first + zf.rename(oldName, newName) + 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?) + zfRead.close + + zf.close + res = system("unzip -tqq #{TEST_ZIP.zip_name}") + assert_equal(res, true) + 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') + zf = ::Zip::File.open(filename, ::Zip::File::CREATE) + zf.add('test1.txt', 'test/data/generated/test_double_commit1.txt') + zf.commit + zf.add('test2.txt', 'test/data/generated/test_double_commit2.txt') + 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) + res = system("unzip -tqq #{filename}") + assert_equal(res, true) + end + + def test_double_commit_zip64 + ::Zip.write_zip64_support = true + test_double_commit('test/data/generated/double_commit_test64.zip') + end + + def test_write_buffer + newName = 'renamedFirst' + zf = ::Zip::File.new(TEST_ZIP.zip_name) + oldName = zf.entries.first + zf.rename(oldName, newName) + 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) + assert(zfRead.entries.detect { |e| e.name == newName } != nil) + assert(zfRead.entries.detect { |e| e.name == oldName }.nil?) + zfRead.close + + zf.close + end + + # This test tests that after commit, you + # can delete the file you used to add the entry to the zip file + # with + def test_commit_use_zip_entry + FileUtils.cp(TestFiles::RANDOM_ASCII_FILE1, OK_DELETE_FILE) + zf = ::Zip::File.open(TEST_ZIP.zip_name) + zf.add('okToDelete.txt', OK_DELETE_FILE) + assert_contains(zf, 'okToDelete.txt') + zf.commit + File.rename(OK_DELETE_FILE, OK_DELETE_MOVED_FILE) + assert_contains(zf, 'okToDelete.txt', OK_DELETE_MOVED_FILE) + end + + # def test_close + # zf = ZipFile.new(TEST_ZIP.zip_name) + # zf.close + # assert_raises(IOError) { + # zf.extract(TEST_ZIP.entry_names.first, "hullubullu") + # } + # end + + def test_compound1 + renamedName = 'renamedName' + filename_to_remove = '' + begin + zf = ::Zip::File.new(TEST_ZIP.zip_name) + originalEntries = 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) + + 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') } + 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) + TestFiles::BINARY_TEST_FILES.each do |filename| + assert_contains(zfRead, filename) + end + assert_not_contains(zfRead, filename_to_remove) + ensure + zfRead.close + end + end + + def test_compound2 + begin + zf = ::Zip::File.new(TEST_ZIP.zip_name) + originalEntries = zf.entries.dup + + originalEntries.each do |entry| + zf.remove(entry) + assert_not_contains(zf, entry) + end + assert(zf.entries.empty?) + + TestFiles::ASCII_TEST_FILES.each do |filename| + zf.add(filename, filename) + assert_contains(zf, filename) + end + assert_equal(zf.entries.sort.map { |e| e.name }, TestFiles::ASCII_TEST_FILES) + + zf.rename(TestFiles::ASCII_TEST_FILES[0], 'newName') + assert_not_contains(zf, TestFiles::ASCII_TEST_FILES[0]) + assert_contains(zf, 'newName') + 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| + assert_contains(zf, filename) + end + + assert_contains(zf, 'newName') + ensure + zfRead.close + end + end + + 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) + end + + def test_preserve_file_order + entryNames = nil + ::Zip::File.open(TEST_ZIP.zip_name) do |zf| + entryNames = zf.entries.map { |e| e.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' + 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 + entries.each do |e| + zf.remove e + zf.get_output_stream(e) { |os| os.write 'foo' } + end + entryNames = entries.map { |e| e.to_s } + end + ::Zip::File.open(TEST_ZIP.zip_name) do |zf| + assert_equal(entryNames, zf.entries.map { |e| e.to_s }) + end + end + + def test_streaming + fname = ::File.join(::File.expand_path(::File.dirname(__FILE__)), '../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| + f.puts File.read(fname) + end + end + + data = nil + File.open(zname, 'rb') do |f| + Zip::File.open_buffer(f) do |zipfile| + zipfile.each do |entry| + next unless entry.name =~ /README.md/ + data = zipfile.read(entry) + end + end + end + assert data + assert data =~ /Simonov/ + end + + def test_nonexistant_zip + assert_raises(::Zip::Error) do + ::Zip::File.open('fake.zip') + end + end + + def test_empty_zip + assert_raises(::Zip::Error) do + ::Zip::File.open(TestFiles::NULL_FILE) + end + end + + def test_odd_extra_field + entry_count = 0 + File.open 'test/data/oddExtraField.zip', 'rb' do |zip_io| + Zip::File.open_buffer zip_io.read do |zip| + zip.each do |_zip_entry| + entry_count += 1 + end + end + end + assert_equal 13, entry_count + end + + def test_open_xls_does_not_raise_type_error + ::Zip::File.open('test/data/test.xls') + end + + 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}") + 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}") + end +end diff --git a/test/filesystem/dir_iterator_test.rb b/test/filesystem/dir_iterator_test.rb new file mode 100644 index 00000000..8d12ce27 --- /dev/null +++ b/test/filesystem/dir_iterator_test.rb @@ -0,0 +1,58 @@ +require 'test_helper' +require 'zip/filesystem' + +class ZipFsDirIteratorTest < MiniTest::Test + FILENAME_ARRAY = %w[f1 f2 f3 f4 f5 f6] + + def setup + @dirIt = ::Zip::FileSystem::ZipFsDirIterator.new(FILENAME_ARRAY) + end + + def test_close + @dirIt.close + assert_raises(IOError, 'closed directory') do + @dirIt.each { |e| p e } + end + assert_raises(IOError, 'closed directory') do + @dirIt.read + end + assert_raises(IOError, 'closed directory') do + @dirIt.rewind + end + assert_raises(IOError, 'closed directory') do + @dirIt.seek(0) + end + assert_raises(IOError, 'closed directory') do + @dirIt.tell + end + end + + def test_each + # Tested through Enumerable.entries + assert_equal(FILENAME_ARRAY, @dirIt.entries) + end + + def test_read + FILENAME_ARRAY.size.times do |i| + assert_equal(FILENAME_ARRAY[i], @dirIt.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) + 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) + end +end diff --git a/test/filesystem/directory_test.rb b/test/filesystem/directory_test.rb new file mode 100644 index 00000000..f36ede53 --- /dev/null +++ b/test/filesystem/directory_test.rb @@ -0,0 +1,139 @@ +require 'test_helper' +require 'zip/filesystem' + +class ZipFsDirectoryTest < MiniTest::Test + TEST_ZIP = 'test/data/generated/zipWithDirs_copy.zip' + GLOB_TEST_ZIP = 'test/data/globTest.zip' + + def setup + FileUtils.cp('test/data/zipWithDirs.zip', TEST_ZIP) + end + + def test_delete + ::Zip::File.open(TEST_ZIP) do |zf| + assert_raises(Errno::ENOENT, 'No such file or directory - NoSuchFile.txt') do + zf.dir.delete('NoSuchFile.txt') + end + assert_raises(Errno::EINVAL, 'Invalid argument - file1') do + zf.dir.delete('file1') + end + assert(zf.file.exists?('dir1')) + zf.dir.delete('dir1') + assert(!zf.file.exists?('dir1')) + end + end + + def test_mkdir + ::Zip::File.open(TEST_ZIP) do |zf| + assert_raises(Errno::EEXIST, 'File exists - dir1') do + zf.dir.mkdir('file1') + end + assert_raises(Errno::EEXIST, 'File exists - dir1') do + zf.dir.mkdir('dir1') + end + assert(!zf.file.exists?('newDir')) + zf.dir.mkdir('newDir') + assert(zf.file.directory?('newDir')) + assert(!zf.file.exists?('newDir2')) + zf.dir.mkdir('newDir2', 3485) + assert(zf.file.directory?('newDir2')) + end + end + + def test_pwd_chdir_entries + ::Zip::File.open(TEST_ZIP) do |zf| + assert_equal('/', zf.dir.pwd) + + assert_raises(Errno::ENOENT, 'No such file or directory - no such dir') do + zf.dir.chdir 'no such dir' + end + + assert_raises(Errno::EINVAL, 'Invalid argument - file1') do + zf.dir.chdir 'file1' + end + + assert_equal(%w[dir1 dir2 file1].sort, zf.dir.entries('.').sort) + zf.dir.chdir 'dir1' + assert_equal('/dir1', zf.dir.pwd) + assert_equal(%w[dir11 file11 file12], zf.dir.entries('.').sort) + + zf.dir.chdir '../dir2/dir21' + assert_equal('/dir2/dir21', zf.dir.pwd) + assert_equal(['dir221'].sort, zf.dir.entries('.').sort) + end + end + + def test_foreach + ::Zip::File.open(TEST_ZIP) do |zf| + blockCalled = false + assert_raises(Errno::ENOENT, 'No such file or directory - noSuchDir') do + zf.dir.foreach('noSuchDir') { |_e| blockCalled = true } + end + assert(!blockCalled) + + assert_raises(Errno::ENOTDIR, 'Not a directory - file1') do + zf.dir.foreach('file1') { |_e| blockCalled = true } + end + assert(!blockCalled) + + entries = [] + zf.dir.foreach('.') { |e| entries << e } + assert_equal(%w[dir1 dir2 file1].sort, entries.sort) + + entries = [] + zf.dir.foreach('dir1') { |e| entries << e } + assert_equal(%w[dir11 file11 file12], entries.sort) + end + end + + def test_chroot + ::Zip::File.open(TEST_ZIP) do |zf| + assert_raises(NotImplementedError) do + zf.dir.chroot + end + end + end + + def test_glob + globbed_files = [ + 'globTest/foo/bar/baz/foo.txt', + 'globTest/foo.txt', + 'globTest/food.txt' + ] + + ::Zip::File.open(GLOB_TEST_ZIP) do |zf| + zf.dir.glob('**/*.txt') do |f| + assert globbed_files.include?(f.name) + end + + zf.dir.glob('globTest/foo/**/*.txt') do |f| + assert_equal globbed_files[0], f.name + end + + zf.dir.chdir('globTest/foo') + zf.dir.glob('**/*.txt') do |f| + assert_equal globbed_files[0], f.name + end + end + end + + def test_open_new + ::Zip::File.open(TEST_ZIP) do |zf| + assert_raises(Errno::ENOTDIR, 'Not a directory - file1') do + zf.dir.new('file1') + end + + assert_raises(Errno::ENOENT, 'No such file or directory - noSuchFile') do + zf.dir.new('noSuchFile') + end + + d = zf.dir.new('.') + assert_equal(%w[file1 dir1 dir2].sort, d.entries.sort) + d.close + + zf.dir.open('dir1') do |dir| + assert_equal(%w[dir11 file11 file12].sort, dir.entries.sort) + end + end + end +end diff --git a/test/filesystem/file_mutating_test.rb b/test/filesystem/file_mutating_test.rb new file mode 100644 index 00000000..ccba6e3d --- /dev/null +++ b/test/filesystem/file_mutating_test.rb @@ -0,0 +1,87 @@ +require 'test_helper' +require 'zip/filesystem' + +class ZipFsFileMutatingTest < MiniTest::Test + TEST_ZIP = 'test/data/generated/zipWithDirs_copy.zip' + def setup + FileUtils.cp('test/data/zipWithDirs.zip', TEST_ZIP) + end + + def teardown; end + + def test_delete + do_test_delete_or_unlink(:delete) + end + + def test_unlink + do_test_delete_or_unlink(:unlink) + end + + def test_open_write + ::Zip::File.open(TEST_ZIP) do |zf| + zf.file.open('test_open_write_entry', 'w') do |f| + f.write "This is what I'm writing" + end + assert_equal("This is what I'm writing", + zf.file.read('test_open_write_entry')) + + # Test with existing entry + zf.file.open('file1', 'wb') do |f| # also check that 'b' option is ignored + f.write "This is what I'm writing too" + end + assert_equal("This is what I'm writing too", + zf.file.read('file1')) + end + end + + def test_rename + ::Zip::File.open(TEST_ZIP) do |zf| + assert_raises(Errno::ENOENT, '') do + zf.file.rename('NoSuchFile', 'bimse') + end + zf.file.rename('file1', 'newNameForFile1') + end + + ::Zip::File.open(TEST_ZIP) do |zf| + assert(!zf.file.exists?('file1')) + assert(zf.file.exists?('newNameForFile1')) + end + end + + def test_chmod + ::Zip::File.open(TEST_ZIP) do |zf| + zf.file.chmod(0o765, 'file1') + end + + ::Zip::File.open(TEST_ZIP) do |zf| + assert_equal(0o100765, zf.file.stat('file1').mode) + end + end + + def do_test_delete_or_unlink(symbol) + ::Zip::File.open(TEST_ZIP) do |zf| + assert(zf.file.exists?('dir2/dir21/dir221/file2221')) + zf.file.send(symbol, 'dir2/dir21/dir221/file2221') + assert(!zf.file.exists?('dir2/dir21/dir221/file2221')) + + assert(zf.file.exists?('dir1/file11')) + assert(zf.file.exists?('dir1/file12')) + zf.file.send(symbol, 'dir1/file11', 'dir1/file12') + assert(!zf.file.exists?('dir1/file11')) + assert(!zf.file.exists?('dir1/file12')) + + assert_raises(Errno::ENOENT) { zf.file.send(symbol, 'noSuchFile') } + assert_raises(Errno::EISDIR) { zf.file.send(symbol, 'dir1/dir11') } + assert_raises(Errno::EISDIR) { zf.file.send(symbol, 'dir1/dir11/') } + end + + ::Zip::File.open(TEST_ZIP) do |zf| + assert(!zf.file.exists?('dir2/dir21/dir221/file2221')) + assert(!zf.file.exists?('dir1/file11')) + assert(!zf.file.exists?('dir1/file12')) + + assert(zf.file.exists?('dir1/dir11')) + assert(zf.file.exists?('dir1/dir11/')) + end + end +end diff --git a/test/filesystem/file_nonmutating_test.rb b/test/filesystem/file_nonmutating_test.rb new file mode 100644 index 00000000..62486666 --- /dev/null +++ b/test/filesystem/file_nonmutating_test.rb @@ -0,0 +1,508 @@ +require 'test_helper' +require 'zip/filesystem' + +class ZipFsFileNonmutatingTest < MiniTest::Test + def setup + @zipsha = Digest::SHA1.file('test/data/zipWithDirs.zip') + @zip_file = ::Zip::File.new('test/data/zipWithDirs.zip') + end + + def teardown + @zip_file.close if @zip_file + assert_equal(@zipsha, Digest::SHA1.file('test/data/zipWithDirs.zip')) + end + + def test_umask + assert_equal(::File.umask, @zip_file.file.umask) + @zip_file.file.umask(0o006) + end + + def test_exists? + assert(!@zip_file.file.exists?('notAFile')) + assert(@zip_file.file.exists?('file1')) + assert(@zip_file.file.exists?('dir1')) + assert(@zip_file.file.exists?('dir1/')) + assert(@zip_file.file.exists?('dir1/file12')) + assert(@zip_file.file.exist?('dir1/file12')) # notice, tests exist? alias of exists? ! + + @zip_file.dir.chdir 'dir1/' + assert(!@zip_file.file.exists?('file1')) + assert(@zip_file.file.exists?('file12')) + end + + def test_open_read + blockCalled = false + @zip_file.file.open('file1', 'r') do |f| + blockCalled = true + assert_equal("this is the entry 'file1' in my test archive!", + f.readline.chomp) + end + assert(blockCalled) + + blockCalled = false + @zip_file.file.open('file1', 'rb') do |f| # test binary flag is ignored + blockCalled = true + assert_equal("this is the entry 'file1' in my test archive!", + f.readline.chomp) + end + assert(blockCalled) + + blockCalled = false + @zip_file.dir.chdir 'dir2' + @zip_file.file.open('file21', 'r') do |f| + blockCalled = true + assert_equal("this is the entry 'dir2/file21' in my test archive!", + f.readline.chomp) + end + assert(blockCalled) + @zip_file.dir.chdir '/' + + assert_raises(Errno::ENOENT) do + @zip_file.file.open('noSuchEntry') + end + + begin + is = @zip_file.file.open('file1') + assert_equal("this is the entry 'file1' in my test archive!", + is.readline.chomp) + ensure + is.close if is + end + end + + def test_new + begin + is = @zip_file.file.new('file1') + assert_equal("this is the entry 'file1' in my test archive!", + is.readline.chomp) + ensure + is.close if is + end + begin + is = @zip_file.file.new('file1') do + fail 'should not call block' + end + ensure + is.close if is + end + end + + def test_symlink + assert_raises(NotImplementedError) do + @zip_file.file.symlink('file1', 'aSymlink') + end + end + + def test_size + assert_raises(Errno::ENOENT) { @zip_file.file.size('notAFile') } + assert_equal(72, @zip_file.file.size('file1')) + assert_equal(0, @zip_file.file.size('dir2/dir21')) + + assert_equal(72, @zip_file.file.stat('file1').size) + assert_equal(0, @zip_file.file.stat('dir2/dir21').size) + end + + def test_size? + assert_nil(@zip_file.file.size?('notAFile')) + assert_equal(72, @zip_file.file.size?('file1')) + assert_nil(@zip_file.file.size?('dir2/dir21')) + + assert_equal(72, @zip_file.file.stat('file1').size?) + assert_nil(@zip_file.file.stat('dir2/dir21').size?) + end + + def test_file? + assert(@zip_file.file.file?('file1')) + assert(@zip_file.file.file?('dir2/file21')) + assert(!@zip_file.file.file?('dir1')) + assert(!@zip_file.file.file?('dir1/dir11')) + + assert(@zip_file.file.stat('file1').file?) + assert(@zip_file.file.stat('dir2/file21').file?) + assert(!@zip_file.file.stat('dir1').file?) + assert(!@zip_file.file.stat('dir1/dir11').file?) + end + + include ExtraAssertions + + def test_dirname + assert_forwarded(File, :dirname, 'retVal', '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 + @zip_file.file.basename('a/b/c/d') + end + end + + def test_split + assert_forwarded(File, :split, 'retVal', 'a/b/c/d') do + @zip_file.file.split('a/b/c/d') + end + end + + def test_join + assert_equal('a/b/c', @zip_file.file.join('a/b', 'c')) + assert_equal('a/b/c/d', @zip_file.file.join('a/b', 'c/d')) + assert_equal('/c/d', @zip_file.file.join('', 'c/d')) + 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')) + assert(!@zip_file.file.send(operation, 'dir1')) + assert(!@zip_file.file.stat('file1').send(operation)) + assert(!@zip_file.file.stat('dir1').send(operation)) + end + + def assert_true_if_entry_exists(operation) + assert(!@zip_file.file.send(operation, 'noSuchFile')) + assert(@zip_file.file.send(operation, 'file1')) + assert(@zip_file.file.send(operation, 'dir1')) + assert(@zip_file.file.stat('file1').send(operation)) + assert(@zip_file.file.stat('dir1').send(operation)) + end + + def test_pipe? + assert_always_false(:pipe?) + end + + def test_blockdev? + assert_always_false(:blockdev?) + end + + def test_symlink? + assert_always_false(:symlink?) + end + + def test_socket? + assert_always_false(:socket?) + end + + def test_chardev? + assert_always_false(:chardev?) + end + + def test_truncate + assert_raises(StandardError, 'truncate not supported') do + @zip_file.file.truncate('file1', 100) + end + end + + def assert_e_n_o_e_n_t(operation, args = ['NoSuchFile']) + assert_raises(Errno::ENOENT) do + @zip_file.file.send(operation, *args) + end + end + + def test_ftype + assert_e_n_o_e_n_t(:ftype) + assert_equal('file', @zip_file.file.ftype('file1')) + assert_equal('directory', @zip_file.file.ftype('dir1/dir11')) + assert_equal('directory', @zip_file.file.ftype('dir1/dir11/')) + end + + def test_link + assert_raises(NotImplementedError) do + @zip_file.file.link('file1', 'someOtherString') + end + end + + def test_directory? + assert(!@zip_file.file.directory?('notAFile')) + assert(!@zip_file.file.directory?('file1')) + assert(!@zip_file.file.directory?('dir1/file11')) + assert(@zip_file.file.directory?('dir1')) + assert(@zip_file.file.directory?('dir1/')) + assert(@zip_file.file.directory?('dir2/dir21')) + + assert(!@zip_file.file.stat('file1').directory?) + assert(!@zip_file.file.stat('dir1/file11').directory?) + assert(@zip_file.file.stat('dir1').directory?) + assert(@zip_file.file.stat('dir1/').directory?) + assert(@zip_file.file.stat('dir2/dir21').directory?) + end + + def test_chown + assert_equal(2, @zip_file.file.chown(1, 2, 'dir1', 'file1')) + assert_equal(1, @zip_file.file.stat('dir1').uid) + assert_equal(2, @zip_file.file.stat('dir1').gid) + assert_equal(2, @zip_file.file.chown(nil, nil, 'dir1', 'file1')) + end + + def test_zero? + assert(!@zip_file.file.zero?('notAFile')) + assert(!@zip_file.file.zero?('file1')) + assert(@zip_file.file.zero?('dir1')) + blockCalled = false + ::Zip::File.open('test/data/generated/5entry.zip') do |zf| + blockCalled = true + assert(zf.file.zero?('test/data/generated/empty.txt')) + end + assert(blockCalled) + + assert(!@zip_file.file.stat('file1').zero?) + assert(@zip_file.file.stat('dir1').zero?) + blockCalled = false + ::Zip::File.open('test/data/generated/5entry.zip') do |zf| + blockCalled = true + assert(zf.file.stat('test/data/generated/empty.txt').zero?) + end + assert(blockCalled) + end + + def test_expand_path + ::Zip::File.open('test/data/zipWithDirs.zip') do |zf| + assert_equal('/', zf.file.expand_path('.')) + zf.dir.chdir 'dir1' + assert_equal('/dir1', zf.file.expand_path('.')) + assert_equal('/dir1/file12', zf.file.expand_path('file12')) + assert_equal('/', zf.file.expand_path('..')) + assert_equal('/dir2/dir21', zf.file.expand_path('../dir2/dir21')) + end + end + + def test_mtime + assert_equal(::Zip::DOSTime.at(1_027_694_306), + @zip_file.file.mtime('dir2/file21')) + assert_equal(::Zip::DOSTime.at(1_027_690_863), + @zip_file.file.mtime('dir2/dir21')) + assert_raises(Errno::ENOENT) do + @zip_file.file.mtime('noSuchEntry') + end + + assert_equal(::Zip::DOSTime.at(1_027_694_306), + @zip_file.file.stat('dir2/file21').mtime) + assert_equal(::Zip::DOSTime.at(1_027_690_863), + @zip_file.file.stat('dir2/dir21').mtime) + end + + def test_ctime + assert_nil(@zip_file.file.ctime('file1')) + assert_nil(@zip_file.file.stat('file1').ctime) + end + + def test_atime + assert_nil(@zip_file.file.atime('file1')) + assert_nil(@zip_file.file.stat('file1').atime) + end + + def test_ntfs_time + ::Zip::File.open('test/data/ntfs.zip') do |zf| + t = ::Zip::DOSTime.at(1_410_496_497.405178) + assert_equal(zf.file.mtime('data.txt'), t) + assert_equal(zf.file.atime('data.txt'), t) + assert_equal(zf.file.ctime('data.txt'), t) + end + end + + def test_readable? + assert(!@zip_file.file.readable?('noSuchFile')) + assert(@zip_file.file.readable?('file1')) + assert(@zip_file.file.readable?('dir1')) + assert(@zip_file.file.stat('file1').readable?) + assert(@zip_file.file.stat('dir1').readable?) + end + + def test_readable_real? + assert(!@zip_file.file.readable_real?('noSuchFile')) + assert(@zip_file.file.readable_real?('file1')) + assert(@zip_file.file.readable_real?('dir1')) + assert(@zip_file.file.stat('file1').readable_real?) + assert(@zip_file.file.stat('dir1').readable_real?) + end + + def test_writable? + assert(!@zip_file.file.writable?('noSuchFile')) + assert(@zip_file.file.writable?('file1')) + assert(@zip_file.file.writable?('dir1')) + assert(@zip_file.file.stat('file1').writable?) + assert(@zip_file.file.stat('dir1').writable?) + end + + def test_writable_real? + assert(!@zip_file.file.writable_real?('noSuchFile')) + assert(@zip_file.file.writable_real?('file1')) + assert(@zip_file.file.writable_real?('dir1')) + assert(@zip_file.file.stat('file1').writable_real?) + assert(@zip_file.file.stat('dir1').writable_real?) + end + + def test_executable? + assert(!@zip_file.file.executable?('noSuchFile')) + assert(!@zip_file.file.executable?('file1')) + assert(@zip_file.file.executable?('dir1')) + assert(!@zip_file.file.stat('file1').executable?) + assert(@zip_file.file.stat('dir1').executable?) + end + + def test_executable_real? + assert(!@zip_file.file.executable_real?('noSuchFile')) + assert(!@zip_file.file.executable_real?('file1')) + assert(@zip_file.file.executable_real?('dir1')) + assert(!@zip_file.file.stat('file1').executable_real?) + assert(@zip_file.file.stat('dir1').executable_real?) + end + + def test_owned? + assert_true_if_entry_exists(:owned?) + end + + def test_grpowned? + assert_true_if_entry_exists(:grpowned?) + end + + def test_setgid? + assert_always_false(:setgid?) + end + + def test_setuid? + assert_always_false(:setgid?) + end + + def test_sticky? + assert_always_false(:sticky?) + end + + def test_readlink + assert_raises(NotImplementedError) do + @zip_file.file.readlink('someString') + end + end + + def test_stat + s = @zip_file.file.stat('file1') + assert(s.kind_of?(File::Stat)) # It pretends + assert_raises(Errno::ENOENT, 'No such file or directory - noSuchFile') do + @zip_file.file.stat('noSuchFile') + end + end + + def test_lstat + assert(@zip_file.file.lstat('file1').file?) + end + + def test_pipe + assert_raises(NotImplementedError) do + @zip_file.file.pipe + end + end + + def test_foreach + ::Zip::File.open('test/data/generated/zipWithDir.zip') do |zf| + ref = [] + File.foreach('test/data/file1.txt') { |e| ref << e } + index = 0 + + 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 + assert_equal(ref[index], newline) + index = index.next + end + assert_equal(ref.size, index) + end + + ::Zip::File.open('test/data/generated/zipWithDir.zip') do |zf| + ref = [] + File.foreach('test/data/file1.txt', ' ') { |e| ref << e } + index = 0 + + 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 + assert_equal(ref[index], newline) + index = index.next + end + assert_equal(ref.size, index) + end + end + + 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'] + }.each do |spec, expected_results| + results = zf.glob(spec) + assert results.all? { |entry| entry.is_a? ::Zip::Entry } + + result_strings = results.map(&:to_s) + missing_matches = expected_results - result_strings + extra_matches = result_strings - expected_results + + assert extra_matches.empty?, "spec #{spec.inspect} has extra results #{extra_matches.inspect}" + assert missing_matches.empty?, "spec #{spec.inspect} missing results #{missing_matches.inspect}" + end + end + + ::Zip::File.open('test/data/globTest.zip') do |zf| + results = [] + zf.glob('**/foo.txt') do |match| + results << "<#{match.class.name}: #{match}>" + end + assert(!results.empty?, 'block not run, or run out of context') + assert_equal 2, results.size + assert_operator results, :include?, '' + assert_operator results, :include?, '' + end + end + + 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\).*$/, '') + assert_equal(system_dir, zipfile_dir) + else + assert_equal(::File.popen('ls') { |f| f.read }, + @zip_file.file.popen('ls') { |f| f.read }) + end + end + + # Can be added later + # def test_select + # fail "implement test" + # end + + def test_readlines + ::Zip::File.open('test/data/generated/zipWithDir.zip') do |zf| + orig_file = ::File.readlines('test/data/file1.txt') + 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 + + assert_equal(orig_file, zip_file) + end + end + + def test_read + ::Zip::File.open('test/data/generated/zipWithDir.zip') do |zf| + orig_file = ::File.read('test/data/file1.txt') + + # 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") + else + zf.file.read('test/data/file1.txt') + end + assert_equal(orig_file, zip_file) + end + end +end diff --git a/test/filesystem/file_stat_test.rb b/test/filesystem/file_stat_test.rb new file mode 100644 index 00000000..05d7fff8 --- /dev/null +++ b/test/filesystem/file_stat_test.rb @@ -0,0 +1,64 @@ +require 'test_helper' +require 'zip/filesystem' + +class ZipFsFileStatTest < MiniTest::Test + def setup + @zip_file = ::Zip::File.new('test/data/zipWithDirs.zip') + end + + def teardown + @zip_file.close if @zip_file + end + + def test_blocks + assert_nil(@zip_file.file.stat('file1').blocks) + end + + def test_ino + assert_equal(0, @zip_file.file.stat('file1').ino) + end + + def test_uid + assert_equal(0, @zip_file.file.stat('file1').uid) + end + + def test_gid + assert_equal(0, @zip_file.file.stat('file1').gid) + end + + def test_ftype + assert_equal('file', @zip_file.file.stat('file1').ftype) + assert_equal('directory', @zip_file.file.stat('dir1').ftype) + end + + def test_mode + assert_equal(0o600, @zip_file.file.stat('file1').mode & 0o777) + assert_equal(0o600, @zip_file.file.stat('file1').mode & 0o777) + assert_equal(0o755, @zip_file.file.stat('dir1').mode & 0o777) + assert_equal(0o755, @zip_file.file.stat('dir1').mode & 0o777) + end + + def test_dev + assert_equal(0, @zip_file.file.stat('file1').dev) + end + + def test_rdev + assert_equal(0, @zip_file.file.stat('file1').rdev) + end + + def test_rdev_major + assert_equal(0, @zip_file.file.stat('file1').rdev_major) + end + + def test_rdev_minor + assert_equal(0, @zip_file.file.stat('file1').rdev_minor) + end + + def test_nlink + assert_equal(1, @zip_file.file.stat('file1').nlink) + end + + def test_blksize + assert_nil(@zip_file.file.stat('file1').blksize) + end +end diff --git a/test/gentestfiles.rb b/test/gentestfiles.rb index e3bc6be4..3e76e7d0 100755 --- a/test/gentestfiles.rb +++ b/test/gentestfiles.rb @@ -3,160 +3,124 @@ $VERBOSE = true class TestFiles - RANDOM_ASCII_FILE1 = "data/generated/randomAscii1.txt" - RANDOM_ASCII_FILE2 = "data/generated/randomAscii2.txt" - RANDOM_ASCII_FILE3 = "data/generated/randomAscii3.txt" - RANDOM_BINARY_FILE1 = "data/generated/randomBinary1.bin" - RANDOM_BINARY_FILE2 = "data/generated/randomBinary2.bin" + RANDOM_ASCII_FILE1 = 'test/data/generated/randomAscii1.txt' + RANDOM_ASCII_FILE2 = 'test/data/generated/randomAscii2.txt' + RANDOM_ASCII_FILE3 = 'test/data/generated/randomAscii3.txt' + RANDOM_BINARY_FILE1 = 'test/data/generated/randomBinary1.bin' + RANDOM_BINARY_FILE2 = 'test/data/generated/randomBinary2.bin' - EMPTY_TEST_DIR = "data/generated/emptytestdir" + NULL_FILE = 'test/data/generated/null.zip' # Zero length, so not a zip file. - 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 ] - TEST_FILES = [ ASCII_TEST_FILES, BINARY_TEST_FILES, EMPTY_TEST_DIR ].flatten! + EMPTY_TEST_DIR = 'test/data/generated/emptytestdir' - def TestFiles.create_test_files(recreate) - if (recreate || - ! (TEST_FILES.inject(true) { |accum, element| accum && File.exists?(element) })) - - Dir.mkdir "data/generated" rescue Errno::EEXIST + 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] + TEST_FILES = [ASCII_TEST_FILES, BINARY_TEST_FILES, EMPTY_TEST_DIR].flatten! + + class << self + def create_test_files + Dir.mkdir 'test/data/generated' unless Dir.exist?('test/data/generated') ASCII_TEST_FILES.each_with_index do |filename, index| - create_random_ascii(filename, 1E4 * (index+1)) + create_random_ascii(filename, 1E4 * (index + 1)) end - + BINARY_TEST_FILES.each_with_index do |filename, index| - create_random_binary(filename, 1E4 * (index+1)) + create_random_binary(filename, 1E4 * (index + 1)) end + system("touch #{NULL_FILE}") + ensure_dir(EMPTY_TEST_DIR) end - end - private - def TestFiles.create_random_ascii(filename, size) - File.open(filename, "wb") do |file| - while (file.tell < size) - file << rand + private + + def create_random_ascii(filename, size) + File.open(filename, 'wb') do |file| + file << rand while file.tell < size end end - end - def TestFiles.create_random_binary(filename, size) - File.open(filename, "wb") do |file| - while (file.tell < size) - file << [rand].pack("V") + def create_random_binary(filename, size) + File.open(filename, 'wb') do |file| + file << [rand].pack('V') while file.tell < size end end - end - def TestFiles.ensure_dir(name) - if File.exists?(name) - return if File.stat(name).directory? - File.delete(name) + def ensure_dir(name) + if File.exist?(name) + return if File.stat(name).directory? + File.delete(name) + end + Dir.mkdir(name) end - Dir.mkdir(name) end - end - - # For representation and creation of # test data class TestZipFile attr_accessor :zip_name, :entry_names, :comment - def initialize(zip_name, entry_names, comment = "") - @zip_name=zip_name - @entry_names=entry_names + def initialize(zip_name, entry_names, comment = '') + @zip_name = zip_name + @entry_names = entry_names @comment = comment end - def TestZipFile.create_test_zips(recreate) - files = Dir.entries("data/generated") - if (recreate || - ! (files.index(File.basename(TEST_ZIP1.zip_name)) && - files.index(File.basename(TEST_ZIP2.zip_name)) && - files.index(File.basename(TEST_ZIP3.zip_name)) && - files.index(File.basename(TEST_ZIP4.zip_name)) && - files.index("empty.txt") && - files.index("empty_chmod640.txt") && - files.index("short.txt") && - files.index("longAscii.txt") && - files.index("longBinary.bin") )) - - raise "failed to create test zip '#{TEST_ZIP1.zip_name}'" unless - system("zip #{TEST_ZIP1.zip_name} data/file2.txt") - raise "failed to remove entry from '#{TEST_ZIP1.zip_name}'" unless - system("zip #{TEST_ZIP1.zip_name} -d data/file2.txt") - - File.open("data/generated/empty.txt", "w") {} - File.open("data/generated/empty_chmod640.txt", "w") { |f| f.chmod(0640) } - - File.open("data/generated/short.txt", "w") { |file| file << "ABCDEF" } - ziptestTxt="" - File.open("data/file2.txt") { |file| ziptestTxt=file.read } - File.open("data/generated/longAscii.txt", "w") do |file| - while (file.tell < 1E5) - file << ziptestTxt - end - end - - testBinaryPattern="" - File.open("data/generated/empty.zip") { |file| testBinaryPattern=file.read } - testBinaryPattern *= 4 - - File.open("data/generated/longBinary.bin", "wb") do |file| - while (file.tell < 6E5) - file << testBinaryPattern << rand << "\0" - end - 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") + + File.open('test/data/generated/empty.txt', 'w') {} + File.open('test/data/generated/empty_chmod640.txt', 'w') {} + ::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 } + File.open('test/data/generated/longAscii.txt', 'w') do |file| + file << ziptestTxt while file.tell < 1E5 + end + + testBinaryPattern = '' + File.open('test/data/generated/empty.zip') { |file| testBinaryPattern = file.read } + testBinaryPattern *= 4 + + File.open('test/data/generated/longBinary.bin', 'wb') do |file| + file << testBinaryPattern << rand << "\0" while file.tell < 6E5 + end - raise "failed to create test zip '#{TEST_ZIP2.zip_name}'" unless - system("zip #{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}| zip -z #{TEST_ZIP2.zip_name}\"") - else + 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}\"") + 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} | zip -z #{TEST_ZIP2.zip_name}\"") - end + 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("zip #{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("zip #{TEST_ZIP4.zip_name} #{TEST_ZIP4.entry_names.join(' ')}") - end + 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 # 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 # that works with the above code raise $!.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." + "\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.' end - TEST_ZIP1 = TestZipFile.new("data/generated/empty.zip", []) - TEST_ZIP2 = TestZipFile.new("data/generated/5entry.zip", %w{ data/generated/longAscii.txt data/generated/empty.txt data/generated/empty_chmod640.txt data/generated/short.txt data/generated/longBinary.bin}, - "my zip comment") - TEST_ZIP3 = TestZipFile.new("data/generated/test1.zip", %w{ data/file1.txt }) - TEST_ZIP4 = TestZipFile.new("data/generated/zipWithDir.zip", [ "data/file1.txt", - TestFiles::EMPTY_TEST_DIR]) + 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_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 - - -END { - TestFiles::create_test_files(ARGV.index("recreate") != nil || - ARGV.index("recreateonly") != nil) - TestZipFile::create_test_zips(ARGV.index("recreate") != nil || - ARGV.index("recreateonly") != nil) - exit if ARGV.index("recreateonly") != nil -} diff --git a/test/inflater_test.rb b/test/inflater_test.rb new file mode 100644 index 00000000..7748c94f --- /dev/null +++ b/test/inflater_test.rb @@ -0,0 +1,14 @@ +require 'test_helper' +class InflaterTest < MiniTest::Test + include DecompressorTests + + def setup + super + @file = File.new('test/data/file1.txt.deflatedData', 'rb') + @decompressor = ::Zip::Inflater.new(@file) + end + + def teardown + @file.close + end +end diff --git a/test/input_stream_test.rb b/test/input_stream_test.rb new file mode 100644 index 00000000..773ee6b5 --- /dev/null +++ b/test/input_stream_test.rb @@ -0,0 +1,182 @@ +require 'test_helper' + +class ZipInputStreamTest < MiniTest::Test + include AssertEntry + + class IOLike + extend Forwardable + + def initialize(path, mode) + @file = File.new(path, mode) + end + + delegate ::Zip::File::IO_METHODS => :@file + end + + def test_new + zis = ::Zip::InputStream.new(TestZipFile::TEST_ZIP2.zip_name) + assert_stream_contents(zis, TestZipFile::TEST_ZIP2) + assert_equal(true, zis.eof?) + zis.close + end + + def test_open_with_block + ::Zip::InputStream.open(TestZipFile::TEST_ZIP2.zip_name) do |zis| + assert_stream_contents(zis, TestZipFile::TEST_ZIP2) + assert_equal(true, zis.eof?) + end + end + + def test_open_without_block + zis = ::Zip::InputStream.open(File.new(TestZipFile::TEST_ZIP2.zip_name, 'rb')) + assert_stream_contents(zis, TestZipFile::TEST_ZIP2) + end + + def test_open_buffer_with_block + ::Zip::InputStream.open(File.new(TestZipFile::TEST_ZIP2.zip_name, 'rb')) do |zis| + assert_stream_contents(zis, TestZipFile::TEST_ZIP2) + assert_equal(true, zis.eof?) + end + end + + def test_open_string_io_without_block + string_io = ::StringIO.new(::File.read(TestZipFile::TEST_ZIP2.zip_name)) + 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)) + ::Zip::InputStream.open(string_io) do |zis| + assert_stream_contents(zis, TestZipFile::TEST_ZIP2) + assert_equal(true, zis.eof?) + end + end + + def test_open_buffer_without_block + zis = ::Zip::InputStream.open(TestZipFile::TEST_ZIP2.zip_name) + assert_stream_contents(zis, TestZipFile::TEST_ZIP2) + end + + def test_open_io_like_with_block + ::Zip::InputStream.open(IOLike.new(TestZipFile::TEST_ZIP2.zip_name, 'rb')) do |zis| + assert_stream_contents(zis, TestZipFile::TEST_ZIP2) + assert_equal(true, zis.eof?) + end + end + + def test_incomplete_reads + ::Zip::InputStream.open(TestZipFile::TEST_ZIP2.zip_name) do |zis| + entry = zis.get_next_entry # longAscii.txt + assert_equal(false, zis.eof?) + assert_equal(TestZipFile::TEST_ZIP2.entry_names[0], entry.name) + assert !zis.gets.empty? + assert_equal(false, zis.eof?) + entry = zis.get_next_entry # empty.txt + assert_equal(TestZipFile::TEST_ZIP2.entry_names[1], entry.name) + assert_equal(0, entry.size) + assert_nil(zis.gets) + assert_equal(true, zis.eof?) + entry = zis.get_next_entry # empty_chmod640.txt + assert_equal(TestZipFile::TEST_ZIP2.entry_names[2], entry.name) + assert_equal(0, entry.size) + assert_nil(zis.gets) + assert_equal(true, zis.eof?) + entry = zis.get_next_entry # short.txt + assert_equal(TestZipFile::TEST_ZIP2.entry_names[3], entry.name) + assert !zis.gets.empty? + entry = zis.get_next_entry # longBinary.bin + assert_equal(TestZipFile::TEST_ZIP2.entry_names[4], entry.name) + assert !zis.gets.empty? + end + end + + def test_incomplete_reads_from_string_io + string_io = ::StringIO.new(::File.read(TestZipFile::TEST_ZIP2.zip_name)) + ::Zip::InputStream.open(string_io) do |zis| + entry = zis.get_next_entry # longAscii.txt + assert_equal(false, zis.eof?) + assert_equal(TestZipFile::TEST_ZIP2.entry_names[0], entry.name) + assert !zis.gets.empty? + assert_equal(false, zis.eof?) + entry = zis.get_next_entry # empty.txt + assert_equal(TestZipFile::TEST_ZIP2.entry_names[1], entry.name) + assert_equal(0, entry.size) + assert_nil(zis.gets) + assert_equal(true, zis.eof?) + entry = zis.get_next_entry # empty_chmod640.txt + assert_equal(TestZipFile::TEST_ZIP2.entry_names[2], entry.name) + assert_equal(0, entry.size) + assert_nil(zis.gets) + assert_equal(true, zis.eof?) + entry = zis.get_next_entry # short.txt + assert_equal(TestZipFile::TEST_ZIP2.entry_names[3], entry.name) + assert !zis.gets.empty? + entry = zis.get_next_entry # longBinary.bin + assert_equal(TestZipFile::TEST_ZIP2.entry_names[4], entry.name) + assert !zis.gets.empty? + end + end + + def test_read_with_number_of_bytes_returns_nil_at_eof + ::Zip::InputStream.open(TestZipFile::TEST_ZIP2.zip_name) do |zis| + entry = zis.get_next_entry # longAscii.txt + zis.read(entry.size) + assert_equal(true, zis.eof?) + assert_nil(zis.read(1)) + assert_nil(zis.read(1)) + end + end + + def test_rewind + ::Zip::InputStream.open(TestZipFile::TEST_ZIP2.zip_name) do |zis| + e = zis.get_next_entry + assert_equal(TestZipFile::TEST_ZIP2.entry_names[0], e.name) + + # Do a little reading + buf = '' + buf << zis.read(100) + assert_equal(100, zis.pos) + buf << (zis.gets || '') + buf << (zis.gets || '') + assert_equal(false, zis.eof?) + + zis.rewind + + buf2 = '' + buf2 << zis.read(100) + buf2 << (zis.gets || '') + buf2 << (zis.gets || '') + + assert_equal(buf, buf2) + + zis.rewind + assert_equal(false, zis.eof?) + assert_equal(0, zis.pos) + + assert_entry(e.name, zis, e.name) + end + end + + def test_mix_read_and_gets + ::Zip::InputStream.open(TestZipFile::TEST_ZIP2.zip_name) do |zis| + zis.get_next_entry + assert_equal('#!/usr/bin/env ruby', zis.gets.chomp) + assert_equal(false, zis.eof?) + assert_equal('', zis.gets.chomp) + assert_equal(false, zis.eof?) + assert_equal('$VERBOSE =', zis.read(10)) + assert_equal(false, zis.eof?) + end + end + + def test_ungetc + ::Zip::InputStream.open(TestZipFile::TEST_ZIP2.zip_name) do |zis| + zis.get_next_entry + first_line = zis.gets.chomp + first_line.reverse.bytes.each { |b| zis.ungetc(b) } + assert_equal('#!/usr/bin/env ruby', zis.gets.chomp) + assert_equal('$VERBOSE =', zis.read(10)) + end + end +end diff --git a/test/ioextras/abstract_input_stream_test.rb b/test/ioextras/abstract_input_stream_test.rb new file mode 100644 index 00000000..3ae005d1 --- /dev/null +++ b/test/ioextras/abstract_input_stream_test.rb @@ -0,0 +1,102 @@ +require 'test_helper' +require 'zip/ioextras' + +class AbstractInputStreamTest < MiniTest::Test + # AbstractInputStream subclass that provides a read method + + TEST_LINES = ["Hello world#{$/}", + "this is the second line#{$/}", + 'this is the last line'] + TEST_STRING = TEST_LINES.join + class TestAbstractInputStream + include ::Zip::IOExtras::AbstractInputStream + + def initialize(aString) + super() + @contents = aString + @readPointer = 0 + end + + def sysread(charsToRead, _buf = nil) + retVal = @contents[@readPointer, charsToRead] + @readPointer += charsToRead + retVal + end + + def produce_input + sysread(100) + end + + def input_finished? + @contents[@readPointer].nil? + end + end + + def setup + @io = TestAbstractInputStream.new(TEST_STRING) + end + + def test_gets + assert_equal(TEST_LINES[0], @io.gets) + assert_equal(1, @io.lineno) + assert_equal(TEST_LINES[0].length, @io.pos) + assert_equal(TEST_LINES[1], @io.gets) + assert_equal(2, @io.lineno) + assert_equal(TEST_LINES[2], @io.gets) + assert_equal(3, @io.lineno) + assert_nil(@io.gets) + assert_equal(4, @io.lineno) + end + + 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')) + end + + LONG_LINES = [ + 'x' * 48 + "\r\n", + 'y' * 49 + "\r\n", + 'rest' + ] + + def test_gets_mulit_char_seperator_split + io = TestAbstractInputStream.new(LONG_LINES.join) + assert_equal(LONG_LINES[0], io.gets("\r\n")) + assert_equal(LONG_LINES[1], io.gets("\r\n")) + assert_equal(LONG_LINES[2], io.gets("\r\n")) + end + + 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("\n", io.gets(nil, 1)) + assert_equal('yy', io.gets(nil, 2)) + end + + def test_gets_with_index + assert_equal(TEST_LINES[0], @io.gets(100)) + assert_equal('this', @io.gets(4)) + end + + def test_each_line + lineNumber = 0 + @io.each_line do |line| + assert_equal(TEST_LINES[lineNumber], line) + lineNumber += 1 + end + end + + def test_readlines + assert_equal(TEST_LINES, @io.readlines) + end + + def test_readline + test_gets + begin + @io.readline + fail 'EOFError expected' + rescue EOFError + end + end +end diff --git a/test/ioextras/abstract_output_stream_test.rb b/test/ioextras/abstract_output_stream_test.rb new file mode 100644 index 00000000..3077db43 --- /dev/null +++ b/test/ioextras/abstract_output_stream_test.rb @@ -0,0 +1,106 @@ +require 'test_helper' +require 'zip/ioextras' + +class AbstractOutputStreamTest < MiniTest::Test + class TestOutputStream + include ::Zip::IOExtras::AbstractOutputStream + + attr_accessor :buffer + + def initialize + @buffer = '' + end + + def <<(data) + @buffer << data + self + end + end + + def setup + @output_stream = TestOutputStream.new + + @origCommaSep = $, + @origOutputSep = $\ + end + + def teardown + $, = @origCommaSep + $\ = @origOutputSep + 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) + end + + def test_print + $\ = nil # record separator set to nil + @output_stream.print('hello') + assert_equal('hello', @output_stream.buffer) + + @output_stream.print(' world.') + assert_equal('hello world.', @output_stream.buffer) + + @output_stream.print(' You ok ', 'out ', 'there?') + assert_equal('hello world. You ok out there?', @output_stream.buffer) + + $\ = "\n" + @output_stream.print + assert_equal("hello world. You ok out there?\n", @output_stream.buffer) + + @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_stream.buffer = '' + @output_stream.print('monkey', 'duck', 'zebra') + assert_equal("monkeyXduckXzebra\n", @output_stream.buffer) + + $\ = nil + @output_stream.buffer = '' + @output_stream.print(20) + assert_equal('20', @output_stream.buffer) + end + + def test_printf + @output_stream.printf('%d %04x', 123, 123) + assert_equal('123 007b', @output_stream.buffer) + end + + def test_putc + @output_stream.putc('A') + assert_equal('A', @output_stream.buffer) + @output_stream.putc(65) + assert_equal('AA', @output_stream.buffer) + end + + def test_puts + @output_stream.puts + assert_equal("\n", @output_stream.buffer) + + @output_stream.puts('hello', 'world') + assert_equal("\nhello\nworld\n", @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.puts(%W[hello\n world\n]) + assert_equal("hello\nworld\n", @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.puts(16, 20, 50, 'hello') + assert_equal("16\n20\n50\nhello\n", @output_stream.buffer) + end +end diff --git a/test/ioextras/fake_io_test.rb b/test/ioextras/fake_io_test.rb new file mode 100644 index 00000000..612f442f --- /dev/null +++ b/test/ioextras/fake_io_test.rb @@ -0,0 +1,18 @@ +require 'test_helper' +require 'zip/ioextras' + +class FakeIOTest < MiniTest::Test + class FakeIOUsingClass + include ::Zip::IOExtras::FakeIO + end + + def test_kind_of? + obj = FakeIOUsingClass.new + + assert(obj.kind_of?(Object)) + assert(obj.kind_of?(FakeIOUsingClass)) + assert(obj.kind_of?(IO)) + assert(!obj.kind_of?(Integer)) + assert(!obj.kind_of?(String)) + end +end diff --git a/test/ioextrastest.rb b/test/ioextrastest.rb deleted file mode 100755 index ef8c63cf..00000000 --- a/test/ioextrastest.rb +++ /dev/null @@ -1,234 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -$: << "../lib" - -require 'test/unit' -require 'zip/ioextras' - -include IOExtras - -class FakeIOTest < Test::Unit::TestCase - class FakeIOUsingClass - include FakeIO - end - - def test_kind_of? - obj = FakeIOUsingClass.new - - assert(obj.kind_of?(Object)) - assert(obj.kind_of?(FakeIOUsingClass)) - assert(obj.kind_of?(IO)) - assert(!obj.kind_of?(Fixnum)) - assert(!obj.kind_of?(String)) - end -end - -class AbstractInputStreamTest < Test::Unit::TestCase - # AbstractInputStream subclass that provides a read method - - TEST_LINES = [ "Hello world#{$/}", - "this is the second line#{$/}", - "this is the last line"] - TEST_STRING = TEST_LINES.join - class TestAbstractInputStream - include AbstractInputStream - def initialize(aString) - super() - @contents = aString - @readPointer = 0 - end - - def sysread(charsToRead, buf = nil) - retVal=@contents[@readPointer, charsToRead] - @readPointer+=charsToRead - return retVal - end - - def produce_input - sysread(100) - end - - def input_finished? - @contents[@readPointer] == nil - end - end - - def setup - @io = TestAbstractInputStream.new(TEST_STRING) - end - - def test_gets - assert_equal(TEST_LINES[0], @io.gets) - assert_equal(1, @io.lineno) - assert_equal(TEST_LINES[0].length, @io.pos) - assert_equal(TEST_LINES[1], @io.gets) - assert_equal(2, @io.lineno) - assert_equal(TEST_LINES[2], @io.gets) - assert_equal(3, @io.lineno) - assert_equal(nil, @io.gets) - assert_equal(4, @io.lineno) - end - - def test_getsMultiCharSeperator - assert_equal("Hell", @io.gets("ll")) - assert_equal("o world#{$/}this is the second l", @io.gets("d l")) - end - - LONG_LINES = [ - 'x'*48 + "\r\n", - 'y'*49 + "\r\n", - 'rest', - ] - def test_getsMulitCharSeperator_split - io = TestAbstractInputStream.new(LONG_LINES.join) - assert_equal(LONG_LINES[0], io.gets("\r\n")) - assert_equal(LONG_LINES[1], io.gets("\r\n")) - assert_equal(LONG_LINES[2], io.gets("\r\n")) - end - - def test_getsWithSepAndIndex - 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("\n", io.gets(nil, 1)) - assert_equal('yy', io.gets(nil, 2)) - end - - def test_getsWithIndex - assert_equal(TEST_LINES[0], @io.gets(100)) - assert_equal('this', @io.gets(4)) - end - - def test_each_line - lineNumber=0 - @io.each_line { - |line| - assert_equal(TEST_LINES[lineNumber], line) - lineNumber+=1 - } - end - - def test_readlines - assert_equal(TEST_LINES, @io.readlines) - end - - def test_readline - test_gets - begin - @io.readline - fail "EOFError expected" - rescue EOFError - end - end -end - -class AbstractOutputStreamTest < Test::Unit::TestCase - class TestOutputStream - include AbstractOutputStream - - attr_accessor :buffer - - def initialize - @buffer = "" - end - - def << (data) - @buffer << data - self - end - end - - def setup - @outputStream = TestOutputStream.new - - @origCommaSep = $, - @origOutputSep = $\ - end - - def teardown - $, = @origCommaSep - $\ = @origOutputSep - end - - def test_write - count = @outputStream.write("a little string") - assert_equal("a little string", @outputStream.buffer) - assert_equal("a little string".length, count) - - count = @outputStream.write(". a little more") - assert_equal("a little string. a little more", @outputStream.buffer) - assert_equal(". a little more".length, count) - end - - def test_print - $\ = nil # record separator set to nil - @outputStream.print("hello") - assert_equal("hello", @outputStream.buffer) - - @outputStream.print(" world.") - assert_equal("hello world.", @outputStream.buffer) - - @outputStream.print(" You ok ", "out ", "there?") - assert_equal("hello world. You ok out there?", @outputStream.buffer) - - $\ = "\n" - @outputStream.print - assert_equal("hello world. You ok out there?\n", @outputStream.buffer) - - @outputStream.print("I sure hope so!") - assert_equal("hello world. You ok out there?\nI sure hope so!\n", @outputStream.buffer) - - $, = "X" - @outputStream.buffer = "" - @outputStream.print("monkey", "duck", "zebra") - assert_equal("monkeyXduckXzebra\n", @outputStream.buffer) - - $\ = nil - @outputStream.buffer = "" - @outputStream.print(20) - assert_equal("20", @outputStream.buffer) - end - - def test_printf - @outputStream.printf("%d %04x", 123, 123) - assert_equal("123 007b", @outputStream.buffer) - end - - def test_putc - @outputStream.putc("A") - assert_equal("A", @outputStream.buffer) - @outputStream.putc(65) - assert_equal("AA", @outputStream.buffer) - end - - def test_puts - @outputStream.puts - assert_equal("\n", @outputStream.buffer) - - @outputStream.puts("hello", "world") - assert_equal("\nhello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts("hello\n", "world\n") - assert_equal("hello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(["hello\n", "world\n"]) - assert_equal("hello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(["hello\n", "world\n"], "bingo") - assert_equal("hello\nworld\nbingo\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(16, 20, 50, "hello") - assert_equal("16\n20\n50\nhello\n", @outputStream.buffer) - end -end - - -# Copyright (C) 2002-2004 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/test/local_entry_test.rb b/test/local_entry_test.rb new file mode 100644 index 00000000..666a63a0 --- /dev/null +++ b/test/local_entry_test.rb @@ -0,0 +1,154 @@ +require 'test_helper' + +class ZipLocalEntryTest < MiniTest::Test + CEH_FILE = 'test/data/generated/centralEntryHeader.bin' + LEH_FILE = 'test/data/generated/localEntryHeader.bin' + + def teardown + ::Zip.write_zip64_support = false + end + + def test_read_local_entry_header_of_first_test_zip_entry + ::File.open(TestZipFile::TEST_ZIP3.zip_name, 'rb') do |file| + entry = ::Zip::Entry.read_local_entry(file) + + assert_equal('', entry.comment) + # Differs from windows and unix because of CR LF + # assert_equal(480, entry.compressed_size) + # assert_equal(0x2a27930f, entry.crc) + # extra field is 21 bytes long + # probably contains some unix attrutes or something + # disabled: assert_equal(nil, entry.extra) + assert_equal(::Zip::Entry::DEFLATED, entry.compression_method) + assert_equal(TestZipFile::TEST_ZIP3.entry_names[0], entry.name) + assert_equal(::File.size(TestZipFile::TEST_ZIP3.entry_names[0]), entry.size) + assert(!entry.directory?) + end + end + + def test_read_date_time + ::File.open('test/data/rubycode.zip', 'rb') do |file| + entry = ::Zip::Entry.read_local_entry(file) + assert_equal('zippedruby1.rb', entry.name) + assert_equal(::Zip::DOSTime.at(1_019_261_638), entry.time) + end + end + + def test_read_local_entry_from_non_zip_file + ::File.open('test/data/file2.txt') do |file| + assert_nil(::Zip::Entry.read_local_entry(file)) + end + 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 + entry = ::Zip::Entry.new + entry.read_local_entry(zipFragment) + fail 'ZipError expected' + rescue ::Zip::Error + end + + def test_write_entry + entry = ::Zip::Entry.new('file.zip', 'entryName', '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) + end + + def test_write_entry_with_zip64 + ::Zip.write_zip64_support = true + entry = ::Zip::Entry.new('file.zip', 'entryName', '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) + end + + def test_write_64entry + ::Zip.write_zip64_support = true + entry = ::Zip::Entry.new('bigfile.zip', 'entryName', '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) + end + + def test_rewrite_local_header64 + ::Zip.write_zip64_support = true + buf1 = StringIO.new + entry = ::Zip::Entry.new('file.zip', 'entryName') + entry.write_local_entry(buf1) + assert(entry.extra['Zip64'].nil?, 'zip64 extra is unnecessarily present') + + buf2 = StringIO.new + entry.size = 0x123456789ABCDEF0 + entry.compressed_size = 0x0123456789ABCDEF + entry.write_local_entry(buf2, 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 + end + + def test_read_local_offset + entry = ::Zip::Entry.new('file.zip', 'entryName') + entry.local_header_offset = 12_345 + ::File.open(CEH_FILE, 'wb') { |f| entry.write_c_dir_entry(f) } + read_entry = nil + ::File.open(CEH_FILE, 'rb') { |f| read_entry = ::Zip::Entry.read_c_dir_entry(f) } + compare_c_dir_entry_headers(entry, read_entry) + end + + def test_read64_local_offset + ::Zip.write_zip64_support = true + entry = ::Zip::Entry.new('file.zip', 'entryName') + entry.local_header_offset = 0x0123456789ABCDEF + ::File.open(CEH_FILE, 'wb') { |f| entry.write_c_dir_entry(f) } + read_entry = nil + ::File.open(CEH_FILE, 'rb') { |f| read_entry = ::Zip::Entry.read_c_dir_entry(f) } + compare_c_dir_entry_headers(entry, read_entry) + end + + private + + def compare_local_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_c_dir_entry_headers(entry1, entry2) + compare_local_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) } + end + + def read_from_file(localFileName, centralFileName) + 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) } + [localEntry, cdirEntry] + end +end diff --git a/test/output_stream_test.rb b/test/output_stream_test.rb new file mode 100644 index 00000000..a7725e22 --- /dev/null +++ b/test/output_stream_test.rb @@ -0,0 +1,128 @@ +require 'test_helper' + +class ZipOutputStreamTest < MiniTest::Test + include AssertEntry + + TEST_ZIP = TestZipFile::TEST_ZIP2.clone + TEST_ZIP.zip_name = 'test/data/generated/output.zip' + + def test_new + zos = ::Zip::OutputStream.new(TEST_ZIP.zip_name) + zos.comment = TEST_ZIP.comment + write_test_zip(zos) + zos.close + assert_test_zip_contents(TEST_ZIP) + end + + def test_open + ::Zip::OutputStream.open(TEST_ZIP.zip_name) do |zos| + zos.comment = TEST_ZIP.comment + write_test_zip(zos) + end + assert_test_zip_contents(TEST_ZIP) + end + + def test_write_buffer + io = ::StringIO.new('') + buffer = ::Zip::OutputStream.write_buffer(io) 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_write_buffer_with_temp_file + tmp_file = Tempfile.new('') + + ::Zip::OutputStream.write_buffer(tmp_file) do |zos| + zos.comment = TEST_ZIP.comment + write_test_zip(zos) + end + + tmp_file.rewind + File.open(TEST_ZIP.zip_name, 'wb') { |f| f.write(tmp_file.read) } + tmp_file.unlink + + 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' } + assert_i_o_error_in_closed_stream { |zos| zos.write 'hello world' } + end + + def test_cannot_open_file + name = TestFiles::EMPTY_TEST_DIR + begin + ::Zip::OutputStream.open(name) + rescue Exception + 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}") + end + end + + def test_put_next_entry + stored_text = 'hello world in stored text' + entry_name = 'file1' + comment = 'my comment' + ::Zip::OutputStream.open(TEST_ZIP.zip_name) do |zos| + zos.put_next_entry(entry_name, comment, nil, ::Zip::Entry::STORED) + zos << stored_text + end + + assert(File.read(TEST_ZIP.zip_name)[stored_text]) + ::Zip::File.open(TEST_ZIP.zip_name) do |zf| + assert_equal(stored_text, zf.read(entry_name)) + end + end + + 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)) + zos.put_next_entry(zip_entry) + zos << file.read + end + + ::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 + end + end + end + + def test_chained_put_into_next_entry + stored_text = 'hello world in stored text' + stored_text2 = 'with chain' + entry_name = 'file1' + comment = 'my comment' + ::Zip::OutputStream.open(TEST_ZIP.zip_name) do |zos| + zos.put_next_entry(entry_name, comment, nil, ::Zip::Entry::STORED) + zos << stored_text << stored_text2 + end + + assert(File.read(TEST_ZIP.zip_name)[stored_text]) + ::Zip::File.open(TEST_ZIP.zip_name) do |zf| + assert_equal(stored_text + stored_text2, zf.read(entry_name)) + end + end + + def assert_i_o_error_in_closed_stream + assert_raises(IOError) do + zos = ::Zip::OutputStream.new('test/data/generated/test_putOnClosedStream.zip') + zos.close + yield zos + end + 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) } + end + end +end diff --git a/test/pass_thru_compressor_test.rb b/test/pass_thru_compressor_test.rb new file mode 100644 index 00000000..334ba90c --- /dev/null +++ b/test/pass_thru_compressor_test.rb @@ -0,0 +1,30 @@ +require 'test_helper' + +class PassThruCompressorTest < MiniTest::Test + include CrcTest + + def test_size + File.open('test/data/generated/dummy.txt', 'wb') do |file| + compressor = ::Zip::PassThruCompressor.new(file) + + assert_equal(0, compressor.size) + + t1 = 'hello world' + t2 = '' + t3 = 'bingo' + + compressor << t1 + assert_equal(compressor.size, t1.size) + + compressor << t2 + assert_equal(compressor.size, t1.size + t2.size) + + compressor << t3 + assert_equal(compressor.size, t1.size + t2.size + t3.size) + end + end + + def test_crc + run_crc_test(::Zip::PassThruCompressor) + end +end diff --git a/test/pass_thru_decompressor_test.rb b/test/pass_thru_decompressor_test.rb new file mode 100644 index 00000000..e0b66892 --- /dev/null +++ b/test/pass_thru_decompressor_test.rb @@ -0,0 +1,14 @@ +require 'test_helper' +class PassThruDecompressorTest < MiniTest::Test + include DecompressorTests + + def setup + super + @file = File.new(TEST_FILE) + @decompressor = ::Zip::PassThruDecompressor.new(@file, File.size(TEST_FILE)) + end + + def teardown + @file.close + end +end diff --git a/test/path_traversal_test.rb b/test/path_traversal_test.rb new file mode 100644 index 00000000..e5bdd722 --- /dev/null +++ b/test/path_traversal_test.rb @@ -0,0 +1,141 @@ +class PathTraversalTest < MiniTest::Test + TEST_FILE_ROOT = File.absolute_path('test/data/path_traversal') + + def setup + # With apologies to anyone using these files... but they are the files in + # the sample zips, so we don't have much choice here. + FileUtils.rm_f '/tmp/moo' + 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 + end + end + end + + def in_tmpdir + Dir.mktmpdir do |tmp| + test_path = File.join(tmp, 'test') + Dir.mkdir test_path + Dir.chdir test_path do + yield test_path + end + end + end + + def test_leading_slash + in_tmpdir do + extract_path_traversal_zip 'jwilk/absolute1.zip' + refute File.exist?('/tmp/moo') + end + end + + def test_multiple_leading_slashes + in_tmpdir do + extract_path_traversal_zip 'jwilk/absolute2.zip' + refute File.exist?('/tmp/moo') + end + end + + def test_leading_dot_dot + in_tmpdir do + extract_path_traversal_zip 'jwilk/relative0.zip' + refute File.exist?('../moo') + end + end + + def test_non_leading_dot_dot_with_existing_folder + in_tmpdir do + extract_path_traversal_zip 'relative1.zip' + assert Dir.exist?('tmp') + refute File.exist?('../moo') + end + end + + def test_non_leading_dot_dot_without_existing_folder + in_tmpdir do + extract_path_traversal_zip 'jwilk/relative2.zip' + refute File.exist?('../moo') + end + end + + def test_file_symlink + in_tmpdir do + extract_path_traversal_zip 'jwilk/symlink.zip' + assert File.exist?('moo') + refute File.exist?('/tmp/moo') + end + end + + def test_directory_symlink + 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 + refute File.exist?('/tmp/moo') + end + end + + def test_two_directory_symlinks_a + 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 + refute File.exist?('cur') + refute File.exist?('par') + refute File.exist?('par/moo') + end + end + + def test_two_directory_symlinks_b + 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 + refute File.exist?('cur') + refute File.exist?('../moo') + end + end + + def test_entry_name_with_absolute_path_does_not_extract + in_tmpdir do + extract_path_traversal_zip 'tuzovakaoff/absolutepath.zip' + 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::File.open(zip_path) do |zip_file| + zip_file.each do |entry| + entry.extract(File.join(test_path, entry.name)) + end + end + refute File.exist?('/tmp/file.txt') + end + end + + def test_entry_name_with_relative_symlink + 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 + refute File.exist?('/tmp/file.txt') + end + end + + def test_entry_name_with_tilde + in_tmpdir do + extract_path_traversal_zip 'tilde.zip' + assert File.exist?('~tilde~') + end + end +end diff --git a/test/samples/example_recursive_test.rb b/test/samples/example_recursive_test.rb new file mode 100644 index 00000000..4fb14883 --- /dev/null +++ b/test/samples/example_recursive_test.rb @@ -0,0 +1,37 @@ +require 'test_helper' +require 'fileutils' +require_relative '../../samples/example_recursive' + +class ExampleRecursiveTest < MiniTest::Test + DIRECTORY_TO_ZIP = 'test/data/globTest' + OUTPUT_DIRECTORY = 'test/data/example_recursive.zip' + TEMP_DIRECTORY = 'test/data/tmp' + + def setup + @generator = ::ZipFileGenerator.new(DIRECTORY_TO_ZIP, OUTPUT_DIRECTORY) + end + + def teardown + FileUtils.rm_rf TEMP_DIRECTORY + FileUtils.rm_f OUTPUT_DIRECTORY + end + + def test_write + @generator.write + unzip + assert_equal Dir.entries(DIRECTORY_TO_ZIP).sort, Dir.entries(TEMP_DIRECTORY).sort + end + + private + + def unzip(file = OUTPUT_DIRECTORY) + Zip::File.open(file) do |zip_file| + zip_file.each do |f| + file_path = File.join(TEMP_DIRECTORY, f.name) + FileUtils.mkdir_p(File.dirname(file_path)) + + zip_file.extract(f, file_path) unless File.exist?(file_path) + end + end + end +end diff --git a/test/settings_test.rb b/test/settings_test.rb new file mode 100644 index 00000000..c2c9cce1 --- /dev/null +++ b/test/settings_test.rb @@ -0,0 +1,95 @@ +require 'test_helper' + +class ZipSettingsTest < MiniTest::Test + # TODO: Refactor out into common test module + include CommonZipFileFixture + + TEST_OUT_NAME = 'test/data/generated/emptyOutDir' + + 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 + end + + def teardown + ::Zip.reset! + end + + def open_zip(&aProc) + assert(!aProc.nil?) + ::Zip::File.open(TestZipFile::TEST_ZIP4.zip_name, &aProc) + end + + def extract_test_dir(&aProc) + open_zip do |zf| + zf.extract(TestFiles::EMPTY_TEST_DIR, TEST_OUT_NAME, &aProc) + end + end + + def test_true_on_exists_proc + Zip.on_exists_proc = true + File.open(TEST_OUT_NAME, 'w') { |f| f.puts 'something' } + extract_test_dir + assert(File.directory?(TEST_OUT_NAME)) + end + + 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 } + end + + def test_false_continue_on_exists_proc + Zip.continue_on_exists_proc = false + + 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 + end + + def test_true_continue_on_exists_proc + Zip.continue_on_exists_proc = true + + replacedEntry = nil + + ::Zip::File.open(TEST_ZIP.zip_name) do |zf| + replacedEntry = zf.entries.first.name + zf.add(replacedEntry, 'test/data/file2.txt') + end + + ::Zip::File.open(TEST_ZIP.zip_name) do |zf| + assert_contains(zf, replacedEntry, 'test/data/file2.txt') + end + end + + def test_false_warn_invalid_date + test_file = File.join(File.dirname(__FILE__), 'data', 'WarnInvalidDate.zip') + Zip.warn_invalid_date = false + + assert_output('', '') do + ::Zip::File.open(test_file) do |_zf| + end + end + end + + 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 + ::Zip::File.open(test_file) do |_zf| + end + end + end + + 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}") + assert_entry_contents(zf, entryName, filename) if File.exist?(filename) + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 00000000..6d11af6c --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,235 @@ +require 'simplecov' +require 'minitest/autorun' +require 'minitest/unit' +require 'fileutils' +require 'tmpdir' +require 'digest/sha1' +require 'zip' +require 'gentestfiles' + +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 + +module IOizeString + attr_reader :tell + + def read(count = nil) + @tell ||= 0 + count = size unless count + retVal = slice(@tell, count) + @tell += count + retVal + end + + def seek(index, offset) + @tell ||= 0 + case offset + when IO::SEEK_END + newPos = size + index + when IO::SEEK_SET + newPos = index + when IO::SEEK_CUR + newPos = @tell + index + else + raise 'Error in test method IOizeString::seek' + end + if newPos < 0 || newPos >= size + raise Errno::EINVAL + else + @tell = newPos + end + end + + def reset + @tell = 0 + end +end + +module DecompressorTests + # expects @refText, @refLines and @decompressor + + TEST_FILE = 'test/data/file1.txt' + + def setup + @refText = '' + File.open(TEST_FILE) { |f| @refText = f.read } + @refLines = @refText.split($/) + end + + def test_read_everything + assert_equal(@refText, @decompressor.sysread) + end + + def test_read_in_chunks + chunkSize = 5 + while (decompressedChunk = @decompressor.sysread(chunkSize)) + assert_equal(@refText.slice!(0, chunkSize), decompressedChunk) + 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.input_finished?) + buf = @decompressor.produce_input + assert_equal(@refText[100...(100 + buf.length)], buf) + end +end + +module AssertEntry + 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) + end + + def assert_entry_contents_for_stream(filename, zis, entryName) + 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' + File.open(zipEntryFilename, 'wb') { |entryfile| entryfile << actual } + fail("File '#{filename}' is different from '#{zipEntryFilename}'") + else + assert_equal(expected, actual) + end + end + end + end + + 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 + end + end + + def assert_stream_contents(zis, testZipFile) + assert(!zis.nil?) + testZipFile.entry_names.each do |entryName| + assert_next_entry(entryName, 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) + 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) + ensure + zis.close if zis + end +end + +module CrcTest + class TestOutputStream + include ::Zip::IOExtras::AbstractOutputStream + + attr_accessor :buffer + + def initialize + @buffer = '' + end + + def <<(data) + @buffer << data + self + end + end + + def run_crc_test(compressorClass) + 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 << str + assert_equal(0x919920fc, deflater.crc) + end +end + +module Enumerable + def compare_enumerables(otherEnumerable) + otherAsArray = otherEnumerable.to_a + each_with_index do |element, index| + return false unless yield(element, otherAsArray[index]) + end + size == otherAsArray.size + end +end + +module CommonZipFileFixture + include AssertEntry + + EMPTY_FILENAME = 'emptyZipFile.zip' + + TEST_ZIP = TestZipFile::TEST_ZIP2.clone + TEST_ZIP.zip_name = 'test/data/generated/5entry_copy.zip' + + def setup + File.delete(EMPTY_FILENAME) if File.exist?(EMPTY_FILENAME) + FileUtils.cp(TestZipFile::TEST_ZIP2.zip_name, TEST_ZIP.zip_name) + end +end + +module ExtraAssertions + def assert_forwarded(anObject, method, retVal, *expectedArgs) + callArgs = nil + setCallArgsProc = proc { |args| callArgs = args } + anObject.instance_eval <<-"end_eval" + alias #{method}_org #{method} + def #{method}(*args) + ObjectSpace._id2ref(#{setCallArgsProc.object_id}).call(args) + ObjectSpace._id2ref(#{retVal.object_id}) + end + end_eval + + assert_equal(retVal, yield) # Invoke test + assert_equal(expectedArgs, callArgs) + ensure + anObject.instance_eval "undef #{method}; alias #{method} #{method}_org" + end +end + +module ZipEntryData + TEST_ZIPFILE = 'someZipFile.zip' + TEST_COMMENT = 'a comment' + TEST_COMPRESSED_SIZE = 1234 + TEST_CRC = 325_324 + TEST_EXTRA = 'Some data here' + TEST_COMPRESSIONMETHOD = ::Zip::Entry::DEFLATED + TEST_NAME = 'entry name' + TEST_SIZE = 8432 + TEST_ISDIRECTORY = false + TEST_TIME = Time.now +end diff --git a/test/unicode_file_names_and_comments_test.rb b/test/unicode_file_names_and_comments_test.rb new file mode 100644 index 00000000..aac3e256 --- /dev/null +++ b/test/unicode_file_names_and_comments_test.rb @@ -0,0 +1,62 @@ +# encoding: utf-8 + +require 'test_helper' + +class ZipUnicodeFileNamesAndComments < MiniTest::Test + FILENAME = File.join(File.dirname(__FILE__), 'test1.zip') + + def test_unicode_file_name + file_entrys = ['текстовыйфайл.txt', 'Résumé.txt', '슬레이어스휘.txt'] + directory_entrys = ['папка/текстовыйфайл.txt', 'Résumé/Résumé.txt', '슬레이어스휘/슬레이어스휘.txt'] + stream = ::Zip::OutputStream.open(FILENAME) do |io| + file_entrys.each do |filename| + io.put_next_entry(filename) + io.write(filename) + end + directory_entrys.each do |filepath| + io.put_next_entry(filepath) + io.write(filepath) + end + end + assert(!stream.nil?) + ::Zip::InputStream.open(FILENAME) do |io| + file_entrys.each do |filename| + entry = io.get_next_entry + entry_name = entry.name + entry_name = entry_name.force_encoding('UTF-8') + assert(filename == entry_name) + end + directory_entrys.each do |filepath| + entry = io.get_next_entry + entry_name = entry.name + entry_name = entry_name.force_encoding('UTF-8') + assert(filepath == entry_name) + end + end + + ::Zip.force_entry_names_encoding = 'UTF-8' + ::Zip::File.open(FILENAME) do |zip| + file_entrys.each do |filename| + refute_nil(zip.find_entry(filename)) + end + directory_entrys.each do |filepath| + refute_nil(zip.find_entry(filepath)) + end + end + ::Zip.force_entry_names_encoding = nil + + ::File.unlink(FILENAME) + end + + def test_unicode_comment + str = '渠道升级' + ::Zip::File.open(FILENAME, Zip::File::CREATE) do |z| + z.comment = str + end + + ::Zip::File.open(FILENAME) do |z| + assert(z.comment.force_encoding('UTF-8') == str) + end + ::File.unlink(FILENAME) + end +end diff --git a/test/zip64_full_test.rb b/test/zip64_full_test.rb new file mode 100644 index 00000000..ed11ed65 --- /dev/null +++ b/test/zip64_full_test.rb @@ -0,0 +1,51 @@ +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 + + def prepare_test_file(test_filename) + ::File.delete(test_filename) if ::File.exist?(test_filename) + test_filename + 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') + io.write(first_text) + + # 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) + 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') + 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' + end + end + +end diff --git a/test/zip64_support_test.rb b/test/zip64_support_test.rb new file mode 100644 index 00000000..3e4154a8 --- /dev/null +++ b/test/zip64_support_test.rb @@ -0,0 +1,14 @@ +require 'test_helper' + +class Zip64SupportTest < MiniTest::Test + TEST_FILE = File.join(File.dirname(__FILE__), 'data', 'zip64-sample.zip') + + def test_open_zip64_file + zip_file = ::Zip::File.open(TEST_FILE) + assert(!zip_file.nil?) + assert(zip_file.entries.count == 2) + test_rb = zip_file.entries.find { |x| x.name == 'test.rb' } + assert(test_rb.size == 482) + assert(test_rb.compressed_size == 229) + end +end diff --git a/test/zipfilesystemtest.rb b/test/zipfilesystemtest.rb deleted file mode 100755 index b2be90d0..00000000 --- a/test/zipfilesystemtest.rb +++ /dev/null @@ -1,893 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -$: << "../lib" - -require 'zip/zipfilesystem' -require 'test/unit' -require 'fileutils' - -require 'digest/sha1' - -module ExtraAssertions - - def assert_forwarded(anObject, method, retVal, *expectedArgs) - callArgs = nil - setCallArgsProc = proc { |args| callArgs = args } - anObject.instance_eval <<-"end_eval" - alias #{method}_org #{method} - def #{method}(*args) - ObjectSpace._id2ref(#{setCallArgsProc.object_id}).call(args) - ObjectSpace._id2ref(#{retVal.object_id}) - end - end_eval - - assert_equal(retVal, yield) # Invoke test - assert_equal(expectedArgs, callArgs) - ensure - anObject.instance_eval "undef #{method}; alias #{method} #{method}_org" - end - -end - -include Zip - -class ZipFsFileNonmutatingTest < Test::Unit::TestCase - def setup - @zipsha = Digest::SHA1.file("data/zipWithDirs.zip") - @zipFile = ZipFile.new("data/zipWithDirs.zip") - end - - def teardown - @zipFile.close if @zipFile - assert_equal(@zipsha, Digest::SHA1.file("data/zipWithDirs.zip")) - end - - def test_umask - assert_equal(File.umask, @zipFile.file.umask) - @zipFile.file.umask(0006) - end - - def test_exists? - assert(! @zipFile.file.exists?("notAFile")) - assert(@zipFile.file.exists?("file1")) - assert(@zipFile.file.exists?("dir1")) - assert(@zipFile.file.exists?("dir1/")) - assert(@zipFile.file.exists?("dir1/file12")) - assert(@zipFile.file.exist?("dir1/file12")) # notice, tests exist? alias of exists? ! - - @zipFile.dir.chdir "dir1/" - assert(!@zipFile.file.exists?("file1")) - assert(@zipFile.file.exists?("file12")) - end - - def test_open_read - blockCalled = false - @zipFile.file.open("file1", "r") { - |f| - blockCalled = true - assert_equal("this is the entry 'file1' in my test archive!", - f.readline.chomp) - } - assert(blockCalled) - - blockCalled = false - @zipFile.file.open("file1", "rb") { # test binary flag is ignored - |f| - blockCalled = true - assert_equal("this is the entry 'file1' in my test archive!", - f.readline.chomp) - } - assert(blockCalled) - - blockCalled = false - @zipFile.dir.chdir "dir2" - @zipFile.file.open("file21", "r") { - |f| - blockCalled = true - assert_equal("this is the entry 'dir2/file21' in my test archive!", - f.readline.chomp) - } - assert(blockCalled) - @zipFile.dir.chdir "/" - - assert_raise(Errno::ENOENT) { - @zipFile.file.open("noSuchEntry") - } - - begin - is = @zipFile.file.open("file1") - assert_equal("this is the entry 'file1' in my test archive!", - is.readline.chomp) - ensure - is.close if is - end - end - - def test_new - begin - is = @zipFile.file.new("file1") - assert_equal("this is the entry 'file1' in my test archive!", - is.readline.chomp) - ensure - is.close if is - end - begin - is = @zipFile.file.new("file1") { - fail "should not call block" - } - ensure - is.close if is - end - end - - def test_symlink - assert_raise(NotImplementedError) { - @zipFile.file.symlink("file1", "aSymlink") - } - end - - def test_size - assert_raise(Errno::ENOENT) { @zipFile.file.size("notAFile") } - assert_equal(72, @zipFile.file.size("file1")) - assert_equal(0, @zipFile.file.size("dir2/dir21")) - - assert_equal(72, @zipFile.file.stat("file1").size) - assert_equal(0, @zipFile.file.stat("dir2/dir21").size) - end - - def test_size? - assert_equal(nil, @zipFile.file.size?("notAFile")) - assert_equal(72, @zipFile.file.size?("file1")) - assert_equal(nil, @zipFile.file.size?("dir2/dir21")) - - assert_equal(72, @zipFile.file.stat("file1").size?) - assert_equal(nil, @zipFile.file.stat("dir2/dir21").size?) - end - - - def test_file? - assert(@zipFile.file.file?("file1")) - assert(@zipFile.file.file?("dir2/file21")) - assert(! @zipFile.file.file?("dir1")) - assert(! @zipFile.file.file?("dir1/dir11")) - - assert(@zipFile.file.stat("file1").file?) - assert(@zipFile.file.stat("dir2/file21").file?) - assert(! @zipFile.file.stat("dir1").file?) - assert(! @zipFile.file.stat("dir1/dir11").file?) - end - - include ExtraAssertions - - def test_dirname - assert_forwarded(File, :dirname, "retVal", "a/b/c/d") { - @zipFile.file.dirname("a/b/c/d") - } - end - - def test_basename - assert_forwarded(File, :basename, "retVal", "a/b/c/d") { - @zipFile.file.basename("a/b/c/d") - } - end - - def test_split - assert_forwarded(File, :split, "retVal", "a/b/c/d") { - @zipFile.file.split("a/b/c/d") - } - end - - def test_join - assert_equal("a/b/c", @zipFile.file.join("a/b", "c")) - assert_equal("a/b/c/d", @zipFile.file.join("a/b", "c/d")) - assert_equal("/c/d", @zipFile.file.join("", "c/d")) - assert_equal("a/b/c/d", @zipFile.file.join("a", "b", "c", "d")) - end - - def test_utime - t_now = DOSTime.now - t_bak = @zipFile.file.mtime("file1") - @zipFile.file.utime(t_now, "file1") - assert_equal(t_now, @zipFile.file.mtime("file1")) - @zipFile.file.utime(t_bak, "file1") - assert_equal(t_bak, @zipFile.file.mtime("file1")) - end - - - def assert_always_false(operation) - assert(! @zipFile.file.send(operation, "noSuchFile")) - assert(! @zipFile.file.send(operation, "file1")) - assert(! @zipFile.file.send(operation, "dir1")) - assert(! @zipFile.file.stat("file1").send(operation)) - assert(! @zipFile.file.stat("dir1").send(operation)) - end - - def assert_true_if_entry_exists(operation) - assert(! @zipFile.file.send(operation, "noSuchFile")) - assert(@zipFile.file.send(operation, "file1")) - assert(@zipFile.file.send(operation, "dir1")) - assert(@zipFile.file.stat("file1").send(operation)) - assert(@zipFile.file.stat("dir1").send(operation)) - end - - def test_pipe? - assert_always_false(:pipe?) - end - - def test_blockdev? - assert_always_false(:blockdev?) - end - - def test_symlink? - assert_always_false(:symlink?) - end - - def test_socket? - assert_always_false(:socket?) - end - - def test_chardev? - assert_always_false(:chardev?) - end - - def test_truncate - assert_raise(StandardError, "truncate not supported") { - @zipFile.file.truncate("file1", 100) - } - end - - def assert_e_n_o_e_n_t(operation, args = ["NoSuchFile"]) - assert_raise(Errno::ENOENT) { - @zipFile.file.send(operation, *args) - } - end - - def test_ftype - assert_e_n_o_e_n_t(:ftype) - assert_equal("file", @zipFile.file.ftype("file1")) - assert_equal("directory", @zipFile.file.ftype("dir1/dir11")) - assert_equal("directory", @zipFile.file.ftype("dir1/dir11/")) - end - - def test_link - assert_raise(NotImplementedError) { - @zipFile.file.link("file1", "someOtherString") - } - end - - def test_directory? - assert(! @zipFile.file.directory?("notAFile")) - assert(! @zipFile.file.directory?("file1")) - assert(! @zipFile.file.directory?("dir1/file11")) - assert(@zipFile.file.directory?("dir1")) - assert(@zipFile.file.directory?("dir1/")) - assert(@zipFile.file.directory?("dir2/dir21")) - - assert(! @zipFile.file.stat("file1").directory?) - assert(! @zipFile.file.stat("dir1/file11").directory?) - assert(@zipFile.file.stat("dir1").directory?) - assert(@zipFile.file.stat("dir1/").directory?) - assert(@zipFile.file.stat("dir2/dir21").directory?) - end - - def test_chown - assert_equal(2, @zipFile.file.chown(1,2, "dir1", "file1")) - assert_equal(1, @zipFile.file.stat("dir1").uid) - assert_equal(2, @zipFile.file.stat("dir1").gid) - assert_equal(2, @zipFile.file.chown(nil, nil, "dir1", "file1")) - end - - def test_zero? - assert(! @zipFile.file.zero?("notAFile")) - assert(! @zipFile.file.zero?("file1")) - assert(@zipFile.file.zero?("dir1")) - blockCalled = false - ZipFile.open("data/generated/5entry.zip") { - |zf| - blockCalled = true - assert(zf.file.zero?("data/generated/empty.txt")) - } - assert(blockCalled) - - assert(! @zipFile.file.stat("file1").zero?) - assert(@zipFile.file.stat("dir1").zero?) - blockCalled = false - ZipFile.open("data/generated/5entry.zip") { - |zf| - blockCalled = true - assert(zf.file.stat("data/generated/empty.txt").zero?) - } - assert(blockCalled) - end - - def test_expand_path - ZipFile.open("data/zipWithDirs.zip") { - |zf| - assert_equal("/", zf.file.expand_path(".")) - zf.dir.chdir "dir1" - assert_equal("/dir1", zf.file.expand_path(".")) - assert_equal("/dir1/file12", zf.file.expand_path("file12")) - assert_equal("/", zf.file.expand_path("..")) - assert_equal("/dir2/dir21", zf.file.expand_path("../dir2/dir21")) - } - end - - def test_mtime - assert_equal(DOSTime.at(1027694306), - @zipFile.file.mtime("dir2/file21")) - assert_equal(DOSTime.at(1027690863), - @zipFile.file.mtime("dir2/dir21")) - assert_raise(Errno::ENOENT) { - @zipFile.file.mtime("noSuchEntry") - } - - assert_equal(DOSTime.at(1027694306), - @zipFile.file.stat("dir2/file21").mtime) - assert_equal(DOSTime.at(1027690863), - @zipFile.file.stat("dir2/dir21").mtime) - end - - def test_ctime - assert_nil(@zipFile.file.ctime("file1")) - assert_nil(@zipFile.file.stat("file1").ctime) - end - - def test_atime - assert_nil(@zipFile.file.atime("file1")) - assert_nil(@zipFile.file.stat("file1").atime) - end - - def test_readable? - assert(! @zipFile.file.readable?("noSuchFile")) - assert(@zipFile.file.readable?("file1")) - assert(@zipFile.file.readable?("dir1")) - assert(@zipFile.file.stat("file1").readable?) - assert(@zipFile.file.stat("dir1").readable?) - end - - def test_readable_real? - assert(! @zipFile.file.readable_real?("noSuchFile")) - assert(@zipFile.file.readable_real?("file1")) - assert(@zipFile.file.readable_real?("dir1")) - assert(@zipFile.file.stat("file1").readable_real?) - assert(@zipFile.file.stat("dir1").readable_real?) - end - - def test_writable? - assert(! @zipFile.file.writable?("noSuchFile")) - assert(@zipFile.file.writable?("file1")) - assert(@zipFile.file.writable?("dir1")) - assert(@zipFile.file.stat("file1").writable?) - assert(@zipFile.file.stat("dir1").writable?) - end - - def test_writable_real? - assert(! @zipFile.file.writable_real?("noSuchFile")) - assert(@zipFile.file.writable_real?("file1")) - assert(@zipFile.file.writable_real?("dir1")) - assert(@zipFile.file.stat("file1").writable_real?) - assert(@zipFile.file.stat("dir1").writable_real?) - end - - def test_executable? - assert(! @zipFile.file.executable?("noSuchFile")) - assert(! @zipFile.file.executable?("file1")) - assert(@zipFile.file.executable?("dir1")) - assert(! @zipFile.file.stat("file1").executable?) - assert(@zipFile.file.stat("dir1").executable?) - end - - def test_executable_real? - assert(! @zipFile.file.executable_real?("noSuchFile")) - assert(! @zipFile.file.executable_real?("file1")) - assert(@zipFile.file.executable_real?("dir1")) - assert(! @zipFile.file.stat("file1").executable_real?) - assert(@zipFile.file.stat("dir1").executable_real?) - end - - def test_owned? - assert_true_if_entry_exists(:owned?) - end - - def test_grpowned? - assert_true_if_entry_exists(:grpowned?) - end - - def test_setgid? - assert_always_false(:setgid?) - end - - def test_setuid? - assert_always_false(:setgid?) - end - - def test_sticky? - assert_always_false(:sticky?) - end - - def test_readlink - assert_raise(NotImplementedError) { - @zipFile.file.readlink("someString") - } - end - - def test_stat - s = @zipFile.file.stat("file1") - assert(s.kind_of?(File::Stat)) # It pretends - assert_raise(Errno::ENOENT, "No such file or directory - noSuchFile") { - @zipFile.file.stat("noSuchFile") - } - end - - def test_lstat - assert(@zipFile.file.lstat("file1").file?) - end - - def test_pipe - assert_raise(NotImplementedError) { - @zipFile.file.pipe - } - end - - def test_foreach - ZipFile.open("data/generated/zipWithDir.zip") do |zf| - ref = [] - File.foreach("data/file1.txt") { |e| ref << e } - index = 0 - - zf.file.foreach("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 - assert_equal(ref[index], newline) - index = index.next - end - assert_equal(ref.size, index) - end - - ZipFile.open("data/generated/zipWithDir.zip") do |zf| - ref = [] - File.foreach("data/file1.txt", " ") { |e| ref << e } - index = 0 - - zf.file.foreach("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 - assert_equal(ref[index], newline) - index = index.next - end - assert_equal(ref.size, index) - end - end - - def test_glob - ZipFile.open('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'] - }.each do |spec,expected_results| - results = zf.glob(spec) - assert results.all?{|entry| entry.is_a? ZipEntry } - - result_strings = results.map(&:to_s) - missing_matches = expected_results - result_strings - extra_matches = result_strings - expected_results - - assert extra_matches.empty?, %Q{spec #{spec.inspect} has extra results #{extra_matches.inspect}} - assert missing_matches.empty?, %Q{spec #{spec.inspect} missing results #{missing_matches.inspect}} - end - end - - ZipFile.open('data/globTest.zip') do |zf| - results = [] - zf.glob('**/foo.txt') do |match| - results << "<#{match.class.name}: #{match.to_s}>" - end - assert((not results.empty?), 'block not run, or run out of context') - assert_equal 2, results.size - assert_operator results, :include?, '' - assert_operator results, :include?, '' - end - end - - 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 = @zipFile.file.popen('dir') { |f| f.read }.gsub(/Dir\(s\).*$/, '') - assert_equal(system_dir, zipfile_dir) - else - assert_equal(File.popen('ls') { |f| f.read }, - @zipFile.file.popen('ls') { |f| f.read }) - end - end - -# Can be added later -# def test_select -# fail "implement test" -# end - - def test_readlines - ZipFile.open("data/generated/zipWithDir.zip") do |zf| - orig_file = File.readlines("data/file1.txt") - zip_file = zf.file.readlines("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 - - assert_equal(orig_file, zip_file) - end - end - - def test_read - ZipFile.open("data/generated/zipWithDir.zip") do |zf| - orig_file = File.read("data/file1.txt") - - #Ruby replaces \n with \r\n automatically on windows - zip_file = Zip::RUNNING_ON_WINDOWS ? \ - zf.file.read("data/file1.txt").gsub(/\r\n/, "\n") : zf.file.read("data/file1.txt") - assert_equal(orig_file, zip_file) - end - end - -end - -class ZipFsFileStatTest < Test::Unit::TestCase - - def setup - @zipFile = ZipFile.new("data/zipWithDirs.zip") - end - - def teardown - @zipFile.close if @zipFile - end - - def test_blocks - assert_equal(nil, @zipFile.file.stat("file1").blocks) - end - - def test_ino - assert_equal(0, @zipFile.file.stat("file1").ino) - end - - def test_uid - assert_equal(0, @zipFile.file.stat("file1").uid) - end - - def test_gid - assert_equal(0, @zipFile.file.stat("file1").gid) - end - - def test_ftype - assert_equal("file", @zipFile.file.stat("file1").ftype) - assert_equal("directory", @zipFile.file.stat("dir1").ftype) - end - - def test_mode - assert_equal(0600, @zipFile.file.stat("file1").mode & 0777) - assert_equal(0600, @zipFile.file.stat("file1").mode & 0777) - assert_equal(0755, @zipFile.file.stat("dir1").mode & 0777) - assert_equal(0755, @zipFile.file.stat("dir1").mode & 0777) - end - - def test_dev - assert_equal(0, @zipFile.file.stat("file1").dev) - end - - def test_rdev - assert_equal(0, @zipFile.file.stat("file1").rdev) - end - - def test_rdev_major - assert_equal(0, @zipFile.file.stat("file1").rdev_major) - end - - def test_rdev_minor - assert_equal(0, @zipFile.file.stat("file1").rdev_minor) - end - - def test_nlink - assert_equal(1, @zipFile.file.stat("file1").nlink) - end - - def test_blksize - assert_nil(@zipFile.file.stat("file1").blksize) - end - -end - -class ZipFsFileMutatingTest < Test::Unit::TestCase - TEST_ZIP = "zipWithDirs_copy.zip" - def setup - FileUtils.cp("data/zipWithDirs.zip", TEST_ZIP) - end - - def teardown - end - - def test_delete - do_test_delete_or_unlink(:delete) - end - - def test_unlink - do_test_delete_or_unlink(:unlink) - end - - def test_open_write - ZipFile.open(TEST_ZIP) { - |zf| - - zf.file.open("test_open_write_entry", "w") { - |f| - f.write "This is what I'm writing" - } - assert_equal("This is what I'm writing", - zf.file.read("test_open_write_entry")) - - # Test with existing entry - zf.file.open("file1", "wb") { #also check that 'b' option is ignored - |f| - f.write "This is what I'm writing too" - } - assert_equal("This is what I'm writing too", - zf.file.read("file1")) - } - end - - def test_rename - ZipFile.open(TEST_ZIP) { - |zf| - assert_raise(Errno::ENOENT, "") { - zf.file.rename("NoSuchFile", "bimse") - } - zf.file.rename("file1", "newNameForFile1") - } - - ZipFile.open(TEST_ZIP) { - |zf| - assert(! zf.file.exists?("file1")) - assert(zf.file.exists?("newNameForFile1")) - } - end - - def test_chmod - ZipFile.open(TEST_ZIP) { - |zf| - - zf.file.chmod(0765, "file1") - } - - ZipFile.open(TEST_ZIP) { - |zf| - assert_equal(0100765, zf.file.stat("file1").mode) - } - end - - def do_test_delete_or_unlink(symbol) - ZipFile.open(TEST_ZIP) { - |zf| - assert(zf.file.exists?("dir2/dir21/dir221/file2221")) - zf.file.send(symbol, "dir2/dir21/dir221/file2221") - assert(! zf.file.exists?("dir2/dir21/dir221/file2221")) - - assert(zf.file.exists?("dir1/file11")) - assert(zf.file.exists?("dir1/file12")) - zf.file.send(symbol, "dir1/file11", "dir1/file12") - assert(! zf.file.exists?("dir1/file11")) - assert(! zf.file.exists?("dir1/file12")) - - assert_raise(Errno::ENOENT) { zf.file.send(symbol, "noSuchFile") } - assert_raise(Errno::EISDIR) { zf.file.send(symbol, "dir1/dir11") } - assert_raise(Errno::EISDIR) { zf.file.send(symbol, "dir1/dir11/") } - } - - ZipFile.open(TEST_ZIP) { - |zf| - assert(! zf.file.exists?("dir2/dir21/dir221/file2221")) - assert(! zf.file.exists?("dir1/file11")) - assert(! zf.file.exists?("dir1/file12")) - - assert(zf.file.exists?("dir1/dir11")) - assert(zf.file.exists?("dir1/dir11/")) - } - end - -end - -class ZipFsDirectoryTest < Test::Unit::TestCase - TEST_ZIP = "zipWithDirs_copy.zip" - - def setup - FileUtils.cp("data/zipWithDirs.zip", TEST_ZIP) - end - - def test_delete - ZipFile.open(TEST_ZIP) { - |zf| - assert_raise(Errno::ENOENT, "No such file or directory - NoSuchFile.txt") { - zf.dir.delete("NoSuchFile.txt") - } - assert_raise(Errno::EINVAL, "Invalid argument - file1") { - zf.dir.delete("file1") - } - assert(zf.file.exists?("dir1")) - zf.dir.delete("dir1") - assert(! zf.file.exists?("dir1")) - } - end - - def test_mkdir - ZipFile.open(TEST_ZIP) { - |zf| - assert_raise(Errno::EEXIST, "File exists - dir1") { - zf.dir.mkdir("file1") - } - assert_raise(Errno::EEXIST, "File exists - dir1") { - zf.dir.mkdir("dir1") - } - assert(!zf.file.exists?("newDir")) - zf.dir.mkdir("newDir") - assert(zf.file.directory?("newDir")) - assert(!zf.file.exists?("newDir2")) - zf.dir.mkdir("newDir2", 3485) - assert(zf.file.directory?("newDir2")) - } - end - - def test_pwd_chdir_entries - ZipFile.open(TEST_ZIP) { - |zf| - assert_equal("/", zf.dir.pwd) - - assert_raise(Errno::ENOENT, "No such file or directory - no such dir") { - zf.dir.chdir "no such dir" - } - - assert_raise(Errno::EINVAL, "Invalid argument - file1") { - zf.dir.chdir "file1" - } - - assert_equal(["dir1", "dir2", "file1"].sort, zf.dir.entries(".").sort) - zf.dir.chdir "dir1" - assert_equal("/dir1", zf.dir.pwd) - assert_equal(["dir11", "file11", "file12"], zf.dir.entries(".").sort) - - zf.dir.chdir "../dir2/dir21" - assert_equal("/dir2/dir21", zf.dir.pwd) - assert_equal(["dir221"].sort, zf.dir.entries(".").sort) - } - end - - def test_foreach - ZipFile.open(TEST_ZIP) { - |zf| - - blockCalled = false - assert_raise(Errno::ENOENT, "No such file or directory - noSuchDir") { - zf.dir.foreach("noSuchDir") { |e| blockCalled = true } - } - assert(! blockCalled) - - assert_raise(Errno::ENOTDIR, "Not a directory - file1") { - zf.dir.foreach("file1") { |e| blockCalled = true } - } - assert(! blockCalled) - - entries = [] - zf.dir.foreach(".") { |e| entries << e } - assert_equal(["dir1", "dir2", "file1"].sort, entries.sort) - - entries = [] - zf.dir.foreach("dir1") { |e| entries << e } - assert_equal(["dir11", "file11", "file12"], entries.sort) - } - end - - def test_chroot - ZipFile.open(TEST_ZIP) { - |zf| - assert_raise(NotImplementedError) { - zf.dir.chroot - } - } - end - - # Globbing not supported yet - #def test_glob - # # test alias []-operator too - # fail "implement test" - #end - - def test_open_new - ZipFile.open(TEST_ZIP) { - |zf| - - assert_raise(Errno::ENOTDIR, "Not a directory - file1") { - zf.dir.new("file1") - } - - assert_raise(Errno::ENOENT, "No such file or directory - noSuchFile") { - zf.dir.new("noSuchFile") - } - - d = zf.dir.new(".") - assert_equal(["file1", "dir1", "dir2"].sort, d.entries.sort) - d.close - - zf.dir.open("dir1") { - |dir| - assert_equal(["dir11", "file11", "file12"].sort, dir.entries.sort) - } - } - end - -end - -class ZipFsDirIteratorTest < Test::Unit::TestCase - - FILENAME_ARRAY = [ "f1", "f2", "f3", "f4", "f5", "f6" ] - - def setup - @dirIt = ZipFileSystem::ZipFsDirIterator.new(FILENAME_ARRAY) - end - - def test_close - @dirIt.close - assert_raise(IOError, "closed directory") { - @dirIt.each { |e| p e } - } - assert_raise(IOError, "closed directory") { - @dirIt.read - } - assert_raise(IOError, "closed directory") { - @dirIt.rewind - } - assert_raise(IOError, "closed directory") { - @dirIt.seek(0) - } - assert_raise(IOError, "closed directory") { - @dirIt.tell - } - - end - - def test_each - # Tested through Enumerable.entries - assert_equal(FILENAME_ARRAY, @dirIt.entries) - end - - def test_read - FILENAME_ARRAY.size.times { - |i| - assert_equal(FILENAME_ARRAY[i], @dirIt.read) - } - end - - def test_rewind - @dirIt.read - @dirIt.read - assert_equal(FILENAME_ARRAY[2], @dirIt.read) - @dirIt.rewind - assert_equal(FILENAME_ARRAY[0], @dirIt.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) - end - -end - - -# Copyright (C) 2002, 2003 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/test/ziptest.rb b/test/ziptest.rb deleted file mode 100755 index 5c84d457..00000000 --- a/test/ziptest.rb +++ /dev/null @@ -1,1887 +0,0 @@ -#!/usr/bin/env ruby -# encoding: utf-8 - -$VERBOSE = true - -$: << "../lib" - -require 'test/unit' -require 'fileutils' -require 'zip/zip' -require 'gentestfiles' - -include Zip - - -class ZipEntryTest < Test::Unit::TestCase - TEST_ZIPFILE = "someZipFile.zip" - TEST_COMMENT = "a comment" - TEST_COMPRESSED_SIZE = 1234 - TEST_CRC = 325324 - TEST_EXTRA = "Some data here" - TEST_COMPRESSIONMETHOD = ZipEntry::DEFLATED - TEST_NAME = "entry name" - TEST_SIZE = 8432 - TEST_ISDIRECTORY = false - TEST_TIME = Time.now - - def test_constructorAndGetters - entry = ZipEntry.new(TEST_ZIPFILE, - TEST_NAME, - TEST_COMMENT, - TEST_EXTRA, - TEST_COMPRESSED_SIZE, - TEST_CRC, - TEST_COMPRESSIONMETHOD, - TEST_SIZE, - TEST_TIME) - - assert_equal(TEST_COMMENT, entry.comment) - assert_equal(TEST_COMPRESSED_SIZE, entry.compressed_size) - assert_equal(TEST_CRC, entry.crc) - assert_instance_of(Zip::ZipExtraField, entry.extra) - 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) - assert_equal(TEST_ISDIRECTORY, entry.is_directory) - end - - def test_is_directoryAndIsFile - assert(ZipEntry.new(TEST_ZIPFILE, "hello").file?) - assert(! ZipEntry.new(TEST_ZIPFILE, "hello").directory?) - - assert(ZipEntry.new(TEST_ZIPFILE, "dir/hello").file?) - assert(! ZipEntry.new(TEST_ZIPFILE, "dir/hello").directory?) - - assert(ZipEntry.new(TEST_ZIPFILE, "hello/").directory?) - assert(! ZipEntry.new(TEST_ZIPFILE, "hello/").file?) - - assert(ZipEntry.new(TEST_ZIPFILE, "dir/hello/").directory?) - assert(! ZipEntry.new(TEST_ZIPFILE, "dir/hello/").file?) - end - - def test_equality - entry1 = ZipEntry.new("file.zip", "name", "isNotCompared", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry2 = ZipEntry.new("file.zip", "name", "isNotComparedXXX", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry3 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry4 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry5 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 1234, - ZipEntry::DEFLATED, 10000) - entry6 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::DEFLATED, 10000) - entry7 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::STORED, 10000) - entry8 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::STORED, 100000) - - assert_equal(entry1, entry1) - assert_equal(entry1, entry2) - - assert(entry2 != entry3) - assert(entry3 != entry4) - assert(entry4 != entry5) - assert(entry5 != entry6) - assert(entry6 != entry7) - assert(entry7 != entry8) - - assert(entry7 != "hello") - assert(entry7 != 12) - end - - def test_compare - assert_equal(0, (ZipEntry.new("zf.zip", "a") <=> ZipEntry.new("zf.zip", "a"))) - assert_equal(1, (ZipEntry.new("zf.zip", "b") <=> ZipEntry.new("zf.zip", "a"))) - assert_equal(-1, (ZipEntry.new("zf.zip", "a") <=> ZipEntry.new("zf.zip", "b"))) - - entries = [ - ZipEntry.new("zf.zip", "5"), - ZipEntry.new("zf.zip", "1"), - ZipEntry.new("zf.zip", "3"), - ZipEntry.new("zf.zip", "4"), - ZipEntry.new("zf.zip", "0"), - ZipEntry.new("zf.zip", "2") - ] - - entries.sort! - assert_equal("0", entries[0].to_s) - assert_equal("1", entries[1].to_s) - assert_equal("2", entries[2].to_s) - assert_equal("3", entries[3].to_s) - assert_equal("4", entries[4].to_s) - assert_equal("5", entries[5].to_s) - end - - def test_parentAsString - entry1 = ZipEntry.new("zf.zip", "aa") - entry2 = ZipEntry.new("zf.zip", "aa/") - entry3 = ZipEntry.new("zf.zip", "aa/bb") - entry4 = ZipEntry.new("zf.zip", "aa/bb/") - entry5 = ZipEntry.new("zf.zip", "aa/bb/cc") - entry6 = ZipEntry.new("zf.zip", "aa/bb/cc/") - - assert_equal(nil, entry1.parent_as_string) - assert_equal(nil, entry2.parent_as_string) - assert_equal("aa/", entry3.parent_as_string) - assert_equal("aa/", entry4.parent_as_string) - assert_equal("aa/bb/", entry5.parent_as_string) - assert_equal("aa/bb/", entry6.parent_as_string) - end - - def test_entry_name_cannot_start_with_slash - assert_raise(ZipEntryNameError) { ZipEntry.new("zf.zip", "/hej/der") } - end -end - -module IOizeString - attr_reader :tell - - def read(count = nil) - @tell ||= 0 - count = size unless count - retVal = slice(@tell, count) - @tell += count - return retVal - end - - def seek(index, offset) - @tell ||= 0 - case offset - when IO::SEEK_END - newPos = size + index - when IO::SEEK_SET - newPos = index - when IO::SEEK_CUR - newPos = @tell + index - else - raise "Error in test method IOizeString::seek" - end - if (newPos < 0 || newPos >= size) - raise Errno::EINVAL - else - @tell=newPos - end - end - - def reset - @tell = 0 - end -end - -class ZipLocalEntryTest < Test::Unit::TestCase - def test_read_local_entryHeaderOfFirstTestZipEntry - File.open(TestZipFile::TEST_ZIP3.zip_name, "rb") { - |file| - entry = ZipEntry.read_local_entry(file) - - assert_equal("", entry.comment) - # Differs from windows and unix because of CR LF - # assert_equal(480, entry.compressed_size) - # assert_equal(0x2a27930f, entry.crc) - # extra field is 21 bytes long - # probably contains some unix attrutes or something - # disabled: assert_equal(nil, entry.extra) - assert_equal(ZipEntry::DEFLATED, entry.compression_method) - assert_equal(TestZipFile::TEST_ZIP3.entry_names[0], entry.name) - assert_equal(File.size(TestZipFile::TEST_ZIP3.entry_names[0]), entry.size) - assert(! entry.is_directory) - } - end - - def test_readDateTime - File.open("data/rubycode.zip", "rb") { - |file| - entry = ZipEntry.read_local_entry(file) - assert_equal("zippedruby1.rb", entry.name) - assert_equal(DOSTime.at(1019261638), entry.time) - } - end - - def test_read_local_entryFromNonZipFile - File.open("data/file2.txt") { - |file| - assert_equal(nil, ZipEntry.read_local_entry(file)) - } - end - - def test_read_local_entryFromTruncatedZipFile - zipFragment="" - File.open(TestZipFile::TEST_ZIP2.zip_name) { |f| zipFragment = f.read(12) } # local header is at least 30 bytes - zipFragment.extend(IOizeString).reset - entry = ZipEntry.new - entry.read_local_entry(zipFragment) - fail "ZipError expected" - rescue ZipError - end - - def test_writeEntry - entry = ZipEntry.new("file.zip", "entryName", "my little comment", - "thisIsSomeExtraInformation", 100, 987654, - ZipEntry::DEFLATED, 400) - write_to_file("localEntryHeader.bin", "centralEntryHeader.bin", entry) - entryReadLocal, entryReadCentral = read_from_file("localEntryHeader.bin", "centralEntryHeader.bin") - compare_local_entry_headers(entry, entryReadLocal) - compare_c_dir_entry_headers(entry, entryReadCentral) - end - - private - def compare_local_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.localHeaderOffset, entry2.localHeaderOffset) - end - - def compare_c_dir_entry_headers(entry1, entry2) - compare_local_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) } - end - - def read_from_file(localFileName, centralFileName) - localEntry = nil - cdirEntry = nil - File.open(localFileName, "rb") { |f| localEntry = ZipEntry.read_local_entry(f) } - File.open(centralFileName, "rb") { |f| cdirEntry = ZipEntry.read_c_dir_entry(f) } - return [localEntry, cdirEntry] - end -end - - -module DecompressorTests - # expects @refText, @refLines and @decompressor - - TEST_FILE="data/file1.txt" - - def setup - @refText="" - File.open(TEST_FILE) { |f| @refText = f.read } - @refLines = @refText.split($/) - end - - def test_readEverything - assert_equal(@refText, @decompressor.sysread) - end - - def test_readInChunks - chunkSize = 5 - while (decompressedChunk = @decompressor.sysread(chunkSize)) - assert_equal(@refText.slice!(0, chunkSize), decompressedChunk) - end - assert_equal(0, @refText.size) - end - - def test_mixingReadsAndProduceInput - # 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.input_finished?) - buf = @decompressor.produce_input - assert_equal(@refText[100...(100+buf.length)], buf) - end -end - -class InflaterTest < Test::Unit::TestCase - include DecompressorTests - - def setup - super - @file = File.new("data/file1.txt.deflatedData", "rb") - @decompressor = Inflater.new(@file) - end - - def teardown - @file.close - end -end - - -class PassThruDecompressorTest < Test::Unit::TestCase - include DecompressorTests - def setup - super - @file = File.new(TEST_FILE) - @decompressor = PassThruDecompressor.new(@file, File.size(TEST_FILE)) - end - - def teardown - @file.close - end -end - - -module AssertEntry - 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_entryContentsForStream(filename, zis, entryName) - end - - def assert_entryContentsForStream(filename, zis, entryName) - File.open(filename, "rb") { - |file| - expected = file.read - actual = zis.read - if (expected != actual) - 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}'") - else - assert_equal(expected, actual) - end - end - } - end - - def AssertEntry.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 - end - end - - def assert_stream_contents(zis, testZipFile) - assert(zis != nil) - testZipFile.entry_names.each { - |entryName| - assert_next_entry(entryName, zis) - } - assert_equal(nil, zis.get_next_entry) - end - - def assert_test_zip_contents(testZipFile) - ZipInputStream.open(testZipFile.zip_name) { - |zis| - assert_stream_contents(zis, testZipFile) - } - end - - def assert_entryContents(zipFile, entryName, filename = entryName.to_s) - zis = zipFile.get_input_stream(entryName) - assert_entryContentsForStream(filename, zis, entryName) - ensure - zis.close if zis - end -end - - - -class ZipInputStreamTest < Test::Unit::TestCase - include AssertEntry - - def test_new - zis = ZipInputStream.new(TestZipFile::TEST_ZIP2.zip_name) - assert_stream_contents(zis, TestZipFile::TEST_ZIP2) - assert_equal(true, zis.eof?) - zis.close - end - - def test_openWithBlock - ZipInputStream.open(TestZipFile::TEST_ZIP2.zip_name) { - |zis| - assert_stream_contents(zis, TestZipFile::TEST_ZIP2) - assert_equal(true, zis.eof?) - } - end - - def test_openWithoutBlock - zis = ZipInputStream.open_buffer(File.new(TestZipFile::TEST_ZIP2.zip_name, "rb")) - assert_stream_contents(zis, TestZipFile::TEST_ZIP2) - end - - def test_openBufferWithBlock - ZipInputStream.open_buffer(File.new(TestZipFile::TEST_ZIP2.zip_name, "rb")) { - |zis| - assert_stream_contents(zis, TestZipFile::TEST_ZIP2) - assert_equal(true, zis.eof?) - } - end - - def test_openBufferWithoutBlock - zis = ZipInputStream.open(TestZipFile::TEST_ZIP2.zip_name) - assert_stream_contents(zis, TestZipFile::TEST_ZIP2) - end - - def test_incompleteReads - ZipInputStream.open(TestZipFile::TEST_ZIP2.zip_name) { - |zis| - entry = zis.get_next_entry # longAscii.txt - assert_equal(false, zis.eof?) - assert_equal(TestZipFile::TEST_ZIP2.entry_names[0], entry.name) - assert zis.gets.length > 0 - assert_equal(false, zis.eof?) - entry = zis.get_next_entry # empty.txt - assert_equal(TestZipFile::TEST_ZIP2.entry_names[1], entry.name) - assert_equal(0, entry.size) - assert_equal(nil, zis.gets) - assert_equal(true, zis.eof?) - entry = zis.get_next_entry # empty_chmod640.txt - assert_equal(TestZipFile::TEST_ZIP2.entry_names[2], entry.name) - assert_equal(0, entry.size) - assert_equal(nil, zis.gets) - assert_equal(true, zis.eof?) - entry = zis.get_next_entry # short.txt - assert_equal(TestZipFile::TEST_ZIP2.entry_names[3], entry.name) - assert zis.gets.length > 0 - entry = zis.get_next_entry # longBinary.bin - assert_equal(TestZipFile::TEST_ZIP2.entry_names[4], entry.name) - assert zis.gets.length > 0 - } - end - - def test_rewind - ZipInputStream.open(TestZipFile::TEST_ZIP2.zip_name) { - |zis| - e = zis.get_next_entry - assert_equal(TestZipFile::TEST_ZIP2.entry_names[0], e.name) - - # Do a little reading - buf = "" - buf << zis.read(100) - assert_equal(100, zis.pos) - buf << (zis.gets || "") - buf << (zis.gets || "") - assert_equal(false, zis.eof?) - - zis.rewind - - buf2 = "" - buf2 << zis.read(100) - buf2 << (zis.gets || "") - buf2 << (zis.gets || "") - - assert_equal(buf, buf2) - - zis.rewind - assert_equal(false, zis.eof?) - assert_equal(0, zis.pos) - - assert_entry(e.name, zis, e.name) - } - end - - def test_mix_read_and_gets - ZipInputStream.open(TestZipFile::TEST_ZIP2.zip_name) { - |zis| - zis.get_next_entry - assert_equal("#!/usr/bin/env ruby", zis.gets.chomp) - assert_equal(false, zis.eof?) - assert_equal("", zis.gets.chomp) - assert_equal(false, zis.eof?) - assert_equal("$VERBOSE =", zis.read(10)) - assert_equal(false, zis.eof?) - } - end - -end - - -module CrcTest - - class TestOutputStream - include IOExtras::AbstractOutputStream - - attr_accessor :buffer - - def initialize - @buffer = "" - end - - def << (data) - @buffer << data - self - end - end - - def run_crc_test(compressorClass) - 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 << str - assert_equal(0x919920fc, deflater.crc) - end -end - - - -class PassThruCompressorTest < Test::Unit::TestCase - include CrcTest - - def test_size - File.open("dummy.txt", "wb") { - |file| - compressor = PassThruCompressor.new(file) - - assert_equal(0, compressor.size) - - t1 = "hello world" - t2 = "" - t3 = "bingo" - - compressor << t1 - assert_equal(compressor.size, t1.size) - - compressor << t2 - assert_equal(compressor.size, t1.size + t2.size) - - compressor << t3 - assert_equal(compressor.size, t1.size + t2.size + t3.size) - } - end - - def test_crc - run_crc_test(PassThruCompressor) - end -end - -class DeflaterTest < Test::Unit::TestCase - include CrcTest - - def test_outputOperator - txt = load_file("data/file2.txt") - deflate(txt, "deflatertest.bin") - inflatedTxt = inflate("deflatertest.bin") - assert_equal(txt, inflatedTxt) - end - - private - def load_file(fileName) - txt = nil - File.open(fileName, "rb") { |f| txt = f.read } - end - - def deflate(data, fileName) - File.open(fileName, "wb") { - |file| - deflater = Deflater.new(file) - deflater << data - deflater.finish - assert_equal(deflater.size, data.size) - file << "trailing data for zlib with -MAX_WBITS" - } - end - - def inflate(fileName) - txt = nil - File.open(fileName, "rb") { - |file| - inflater = Inflater.new(file) - txt = inflater.sysread - } - end - - def test_crc - run_crc_test(Deflater) - end -end - -class ZipOutputStreamTest < Test::Unit::TestCase - include AssertEntry - - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zip_name = "output.zip" - - def test_new - zos = ZipOutputStream.new(TEST_ZIP.zip_name) - zos.comment = TEST_ZIP.comment - write_test_zip(zos) - zos.close - assert_test_zip_contents(TEST_ZIP) - end - - def test_open - ZipOutputStream.open(TEST_ZIP.zip_name) do |zos| - zos.comment = TEST_ZIP.comment - write_test_zip(zos) - end - assert_test_zip_contents(TEST_ZIP) - end - - def test_write_buffer - buffer = ZipOutputStream.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_writingToClosedStream - assert_i_o_error_in_closed_stream { |zos| zos << "hello world" } - assert_i_o_error_in_closed_stream { |zos| zos.puts "hello world" } - assert_i_o_error_in_closed_stream { |zos| zos.write "hello world" } - end - - def test_cannotOpenFile - name = TestFiles::EMPTY_TEST_DIR - begin - ZipOutputStream.open(name) - rescue Exception - 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}") - end - end - - def test_put_next_entry - stored_text = "hello world in stored text" - entry_name = "file1" - comment = "my comment" - ZipOutputStream.open(TEST_ZIP.zip_name) do |zos| - zos.put_next_entry(entry_name, comment, nil, ZipEntry::STORED) - zos << stored_text - end - - assert(File.read(TEST_ZIP.zip_name)[stored_text]) - ZipFile.open(TEST_ZIP.zip_name) do |zf| - assert_equal(stored_text, zf.read(entry_name)) - end - end - - def test_put_next_entry_using_zip_entry_creates_entries_with_correct_timestamps - file = File.open("data/file2.txt", "rb") - ZipOutputStream.open(TEST_ZIP.zip_name) do |zos| - zip_entry = Zip::ZipEntry.new(zos, file.path, "", "", 0, 0, Zip::ZipEntry::DEFLATED, 0, DOSTime.at(file.mtime)) - zos.put_next_entry(zip_entry) - zos << file.read - end - - Zip::ZipInputStream::open(TEST_ZIP.zip_name) do |io| - while (entry = io.get_next_entry) - assert(DOSTime.at(file.mtime).dos_equals(DOSTime.at(entry.mtime))) # Compare DOS Times, since they are stored with two seconds accuracy - end - end - end - - def assert_i_o_error_in_closed_stream - assert_raise(IOError) { - zos = ZipOutputStream.new("test_putOnClosedStream.zip") - zos.close - yield zos - } - 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) } - end - end -end - - - -module Enumerable - def compare_enumerables(otherEnumerable) - otherAsArray = otherEnumerable.to_a - each_with_index { - |element, index| - return false unless yield(element, otherAsArray[index]) - } - return self.size == otherAsArray.size - end -end - - -class ZipCentralDirectoryEntryTest < Test::Unit::TestCase - - def test_read_from_stream - File.open("data/testDirectory.bin", "rb") { - |file| - entry = ZipEntry.read_c_dir_entry(file) - - assert_equal("longAscii.txt", entry.name) - assert_equal(ZipEntry::DEFLATED, entry.compression_method) - assert_equal(106490, entry.size) - assert_equal(3784, entry.compressed_size) - assert_equal(0xfcd1799c, entry.crc) - assert_equal("", entry.comment) - - entry = ZipEntry.read_c_dir_entry(file) - assert_equal("empty.txt", entry.name) - assert_equal(ZipEntry::STORED, entry.compression_method) - assert_equal(0, entry.size) - assert_equal(0, entry.compressed_size) - assert_equal(0x0, entry.crc) - assert_equal("", entry.comment) - - entry = ZipEntry.read_c_dir_entry(file) - assert_equal("short.txt", entry.name) - assert_equal(ZipEntry::STORED, entry.compression_method) - assert_equal(6, entry.size) - assert_equal(6, entry.compressed_size) - assert_equal(0xbb76fe69, entry.crc) - assert_equal("", entry.comment) - - entry = ZipEntry.read_c_dir_entry(file) - assert_equal("longBinary.bin", entry.name) - assert_equal(ZipEntry::DEFLATED, entry.compression_method) - assert_equal(1000024, entry.size) - assert_equal(70847, entry.compressed_size) - assert_equal(0x10da7d59, entry.crc) - assert_equal("", entry.comment) - - entry = ZipEntry.read_c_dir_entry(file) - assert_equal(nil, entry) -# Fields that are not check by this test: -# version made by 2 bytes -# version needed to extract 2 bytes -# general purpose bit flag 2 bytes -# last mod file time 2 bytes -# last mod file date 2 bytes -# compressed size 4 bytes -# uncompressed size 4 bytes -# disk number start 2 bytes -# internal file attributes 2 bytes -# external file attributes 4 bytes -# relative offset of local header 4 bytes - -# file name (variable size) -# extra field (variable size) -# file comment (variable size) - - } - end - - def test_ReadEntryFromTruncatedZipFile - fragment="" - File.open("data/testDirectory.bin") { |f| fragment = f.read(12) } # cdir entry header is at least 46 bytes - fragment.extend(IOizeString) - entry = ZipEntry.new - entry.read_c_dir_entry(fragment) - fail "ZipError expected" - rescue ZipError - end - -end - - -class ZipEntrySetTest < Test::Unit::TestCase - ZIP_ENTRIES = [ - ZipEntry.new("zipfile.zip", "name1", "comment1"), - ZipEntry.new("zipfile.zip", "name2", "comment1"), - ZipEntry.new("zipfile.zip", "name3", "comment1"), - ZipEntry.new("zipfile.zip", "name4", "comment1"), - ZipEntry.new("zipfile.zip", "name5", "comment1"), - ZipEntry.new("zipfile.zip", "name6", "comment1") - ] - - def setup - @zipEntrySet = ZipEntrySet.new(ZIP_ENTRIES) - end - - def test_include - assert(@zipEntrySet.include?(ZIP_ENTRIES.first)) - assert(! @zipEntrySet.include?(ZipEntry.new("different.zip", "different", "aComment"))) - end - - def test_size - assert_equal(ZIP_ENTRIES.size, @zipEntrySet.size) - assert_equal(ZIP_ENTRIES.size, @zipEntrySet.length) - @zipEntrySet << ZipEntry.new("a", "b", "c") - assert_equal(ZIP_ENTRIES.size + 1, @zipEntrySet.length) - end - - def test_add - zes = ZipEntrySet.new - entry1 = ZipEntry.new("zf.zip", "name1") - entry2 = ZipEntry.new("zf.zip", "name2") - zes << entry1 - assert(zes.include?(entry1)) - zes.push(entry2) - assert(zes.include?(entry2)) - 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.first, entry) - - entry = @zipEntrySet.delete(ZIP_ENTRIES.first) - assert_equal(ZIP_ENTRIES.size - 1, @zipEntrySet.size) - assert_nil(entry) - end - - def test_each - # Tested indirectly via each_with_index - count = 0 - @zipEntrySet.each_with_index { - |entry, index| - assert(ZIP_ENTRIES.include?(entry)) - count = count.succ - } - assert_equal(ZIP_ENTRIES.size, count) - end - - def test_entries - assert_equal(ZIP_ENTRIES.sort, @zipEntrySet.entries.sort) - end - - def test_compound - newEntry = ZipEntry.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)) - - @zipEntrySet.delete(newEntry) - assert_equal(ZIP_ENTRIES.size, @zipEntrySet.size) - end - - def test_dup - copy = @zipEntrySet.dup - assert_equal(@zipEntrySet, copy) - - # demonstrate that this is a deep copy - copy.entries[0].name = "a totally different name" - assert(@zipEntrySet != copy) - end - - def test_parent - entries = [ - ZipEntry.new("zf.zip", "a/"), - ZipEntry.new("zf.zip", "a/b/"), - ZipEntry.new("zf.zip", "a/b/c/") - ] - entrySet = ZipEntrySet.new(entries) - - assert_equal(nil, entrySet.parent(entries[0])) - assert_equal(entries[0], entrySet.parent(entries[1])) - assert_equal(entries[1], entrySet.parent(entries[2])) - end - - def test_glob - res = @zipEntrySet.glob('name[2-4]') - assert_equal(3, res.size) - assert_equal(ZIP_ENTRIES[1,3].sort, res.sort) - end - - def test_glob2 - entries = [ - ZipEntry.new("zf.zip", "a/"), - ZipEntry.new("zf.zip", "a/b/b1"), - ZipEntry.new("zf.zip", "a/b/c/"), - ZipEntry.new("zf.zip", "a/b/c/c1") - ] - entrySet = ZipEntrySet.new(entries) - - assert_equal(entries[0,1], entrySet.glob("*")) -# assert_equal(entries[FIXME], entrySet.glob("**")) -# res = entrySet.glob('a*') -# assert_equal(entries.size, res.size) -# assert_equal(entrySet.map { |e| e.name }, res.map { |e| e.name }) - end -end - - -class ZipCentralDirectoryTest < Test::Unit::TestCase - - def test_read_from_stream - File.open(TestZipFile::TEST_ZIP2.zip_name, "rb") { - |zipFile| - cdir = ZipCentralDirectory.read_from_stream(zipFile) - - assert_equal(TestZipFile::TEST_ZIP2.entry_names.size, cdir.size) - assert(cdir.entries.sort.compare_enumerables(TestZipFile::TEST_ZIP2.entry_names.sort) { - |cdirEntry, testEntryName| - cdirEntry.name == testEntryName - }) - assert_equal(TestZipFile::TEST_ZIP2.comment, cdir.comment) - } - end - - def test_readFromInvalidStream - File.open("data/file2.txt", "rb") { - |zipFile| - cdir = ZipCentralDirectory.new - cdir.read_from_stream(zipFile) - } - fail "ZipError expected!" - rescue ZipError - end - - def test_ReadFromTruncatedZipFile - fragment="" - File.open("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 = ZipCentralDirectory.new - entry.read_from_stream(fragment) - fail "ZipError expected" - rescue ZipError - end - - def test_write_to_stream - entries = [ ZipEntry.new("file.zip", "flimse", "myComment", "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt", "Has a comment too") ] - cdir = ZipCentralDirectory.new(entries, "my zip comment") - File.open("cdirtest.bin", "wb") { |f| cdir.write_to_stream(f) } - cdirReadback = ZipCentralDirectory.new - File.open("cdirtest.bin", "rb") { |f| cdirReadback.read_from_stream(f) } - - assert_equal(cdir.entries.sort, cdirReadback.entries.sort) - end - - def test_equality - cdir1 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "my zip comment") - cdir2 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "my zip comment") - cdir3 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "comment?") - cdir4 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "comment?") - assert_equal(cdir1, cdir1) - assert_equal(cdir1, cdir2) - - assert(cdir1 != cdir3) - assert(cdir2 != cdir3) - assert(cdir2 != cdir3) - assert(cdir3 != cdir4) - - assert(cdir3 != "hello") - end -end - - -class BasicZipFileTest < Test::Unit::TestCase - include AssertEntry - - def setup - @zipFile = ZipFile.new(TestZipFile::TEST_ZIP2.zip_name) - @testEntryNameIndex=0 - end - - def test_entries - assert_equal(TestZipFile::TEST_ZIP2.entry_names.sort, - @zipFile.entries.entries.sort.map {|e| e.name} ) - end - - def test_each - count = 0 - visited = {} - @zipFile.each { - |entry| - assert(TestZipFile::TEST_ZIP2.entry_names.include?(entry.name)) - assert(! visited.include?(entry.name)) - visited[entry.name] = nil - count = count.succ - } - assert_equal(TestZipFile::TEST_ZIP2.entry_names.length, count) - end - - def test_foreach - count = 0 - visited = {} - ZipFile.foreach(TestZipFile::TEST_ZIP2.zip_name) { - |entry| - assert(TestZipFile::TEST_ZIP2.entry_names.include?(entry.name)) - assert(! visited.include?(entry.name)) - visited[entry.name] = nil - count = count.succ - } - assert_equal(TestZipFile::TEST_ZIP2.entry_names.length, count) - end - - def test_get_input_stream - count = 0 - visited = {} - @zipFile.each { - |entry| - assert_entry(entry.name, @zipFile.get_input_stream(entry), entry.name) - assert(! visited.include?(entry.name)) - visited[entry.name] = nil - count = count.succ - } - assert_equal(TestZipFile::TEST_ZIP2.entry_names.length, count) - end - - def test_get_input_streamBlock - fileAndEntryName = @zipFile.entries.first.name - @zipFile.get_input_stream(fileAndEntryName) { - |zis| - assert_entryContentsForStream(fileAndEntryName, - zis, - fileAndEntryName) - } - end -end - -module CommonZipFileFixture - include AssertEntry - - EMPTY_FILENAME = "emptyZipFile.zip" - - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zip_name = "5entry_copy.zip" - - def setup - File.delete(EMPTY_FILENAME) if File.exists?(EMPTY_FILENAME) - FileUtils.cp(TestZipFile::TEST_ZIP2.zip_name, TEST_ZIP.zip_name) - end -end - -class ZipFileTest < Test::Unit::TestCase - include CommonZipFileFixture - - def test_createFromScratchToBuffer - comment = "a short comment" - - buffer = ::ZipFile.add_buffer do |zf| - zf.get_output_stream("myFile") { |os| os.write "myFile contains just this" } - zf.mkdir("dir1") - zf.comment = comment - end - - ::File.open(EMPTY_FILENAME, 'wb') { |file| file.write buffer.string } - # Not sure if the following line was just accidentally left in, but - # it's not related to the tests and breaks on windows - `cp #{EMPTY_FILENAME} ~/test.zip` unless Zip::RUNNING_ON_WINDOWS - - zfRead = ::ZipFile.new(EMPTY_FILENAME) - assert_equal(comment, zfRead.comment) - assert_equal(2, zfRead.entries.length) - end - - def test_createFromScratch - comment = "a short comment" - - zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) - zf.get_output_stream("myFile") { |os| os.write "myFile contains just this" } - zf.mkdir("dir1") - zf.comment = comment - zf.close - - zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equal(comment, zfRead.comment) - assert_equal(2, zfRead.entries.length) - end - - def test_get_output_stream - entryCount = nil - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - entryCount = zf.size - zf.get_output_stream('newEntry.txt') { - |os| - os.write "Putting stuff in newEntry.txt" - } - assert_equal(entryCount+1, zf.size) - assert_equal("Putting stuff in newEntry.txt", zf.read("newEntry.txt")) - - zf.get_output_stream(zf.get_entry('data/generated/empty.txt')) { - |os| - os.write "Putting stuff in data/generated/empty.txt" - } - assert_equal(entryCount+1, zf.size) - assert_equal("Putting stuff in data/generated/empty.txt", zf.read("data/generated/empty.txt")) - - zf.get_output_stream('entry.bin') { - |os| - os.write(File.open('data/generated/5entry.zip', 'rb').read) - } - } - - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - assert_equal(entryCount+2, zf.size) - assert_equal("Putting stuff in newEntry.txt", zf.read("newEntry.txt")) - assert_equal("Putting stuff in data/generated/empty.txt", zf.read("data/generated/empty.txt")) - assert_equal(File.open('data/generated/5entry.zip', 'rb').read, zf.read("entry.bin")) - } - end - - def test_add - srcFile = "data/file2.txt" - entryName = "newEntryName.rb" - assert(File.exists?(srcFile)) - zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) - zf.add(entryName, srcFile) - zf.close - - zfRead = ZipFile.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) { |zis| zis.read }) - end - - def test_addExistingEntryName - assert_raise(ZipEntryExistsError) { - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - zf.add(zf.entries.first.name, "data/file2.txt") - } - } - end - - def test_addExistingEntryNameReplace - gotCalled = false - replacedEntry = nil - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - replacedEntry = zf.entries.first.name - zf.add(replacedEntry, "data/file2.txt") { gotCalled = true; true } - } - assert(gotCalled) - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - assert_contains(zf, replacedEntry, "data/file2.txt") - } - end - - def test_addDirectory - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - zf.add(TestFiles::EMPTY_TEST_DIR, TestFiles::EMPTY_TEST_DIR) - } - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - dirEntry = zf.entries.detect { |e| e.name == TestFiles::EMPTY_TEST_DIR+"/" } - assert(dirEntry.is_directory) - } - end - - def test_remove - entryToRemove, *remainingEntries = TEST_ZIP.entry_names - - FileUtils.cp(TestZipFile::TEST_ZIP2.zip_name, TEST_ZIP.zip_name) - - zf = ZipFile.new(TEST_ZIP.zip_name) - assert(zf.entries.map { |e| e.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) - zf.close - - zfRead = ZipFile.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) - zfRead.close - end - - def test_rename - entryToRename, * = TEST_ZIP.entry_names - - zf = ZipFile.new(TEST_ZIP.zip_name) - assert(zf.entries.map { |e| e.name }.include?(entryToRename)) - - contents = zf.read(entryToRename) - newName = "changed entry name" - assert(! zf.entries.map { |e| e.name }.include?(newName)) - - zf.rename(entryToRename, newName) - assert(zf.entries.map { |e| e.name }.include?(newName)) - - assert_equal(contents, zf.read(newName)) - - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zip_name) - assert(zfRead.entries.map { |e| e.name }.include?(newName)) - assert_equal(contents, zfRead.read(newName)) - zfRead.close - end - - def test_renameToExistingEntry - oldEntries = nil - ZipFile.open(TEST_ZIP.zip_name) { |zf| oldEntries = zf.entries } - - assert_raise(ZipEntryExistsError) do - ZipFile.open(TEST_ZIP.zip_name) do |zf| - zf.rename(zf.entries[0], zf.entries[1].name) - end - end - - ZipFile.open(TEST_ZIP.zip_name) do |zf| - assert_equal(oldEntries.sort.map{ |e| e.name }, zf.entries.sort.map{ |e| e.name }) - end - end - - def test_renameToExistingEntryOverwrite - oldEntries = nil - ZipFile.open(TEST_ZIP.zip_name) { |zf| oldEntries = zf.entries } - - gotCalled = false - renamedEntryName = nil - ZipFile.open(TEST_ZIP.zip_name) do |zf| - renamedEntryName = zf.entries[0].name - zf.rename(zf.entries[0], zf.entries[1].name) { gotCalled = true; true } - end - - assert(gotCalled) - oldEntries.delete_if { |e| e.name == renamedEntryName } - ZipFile.open(TEST_ZIP.zip_name) do |zf| - assert_equal(oldEntries.sort.map{ |e| e.name }, - zf.entries.sort.map{ |e| e.name }) - end - end - - def test_renameNonEntry - nonEntry = "bogusEntry" - target_entry = "target_entryName" - zf = ZipFile.new(TEST_ZIP.zip_name) - assert(! zf.entries.include?(nonEntry)) - assert_raise(Errno::ENOENT) { - zf.rename(nonEntry, target_entry) - } - zf.commit - assert(! zf.entries.include?(target_entry)) - ensure - zf.close - end - - def test_renameEntryToExistingEntry - entry1, entry2, * = TEST_ZIP.entry_names - zf = ZipFile.new(TEST_ZIP.zip_name) - assert_raise(ZipEntryExistsError) { - zf.rename(entry1, entry2) - } - ensure - zf.close - end - - def test_replace - entryToReplace = TEST_ZIP.entry_names[2] - newEntrySrcFilename = "data/file2.txt" - zf = ZipFile.new(TEST_ZIP.zip_name) - zf.replace(entryToReplace, newEntrySrcFilename) - - zf.close - zfRead = ZipFile.new(TEST_ZIP.zip_name) - AssertEntry::assert_contents(newEntrySrcFilename, - zfRead.get_input_stream(entryToReplace) { |is| is.read }) - AssertEntry::assert_contents(TEST_ZIP.entry_names[0], - zfRead.get_input_stream(TEST_ZIP.entry_names[0]) { |is| is.read }) - AssertEntry::assert_contents(TEST_ZIP.entry_names[1], - zfRead.get_input_stream(TEST_ZIP.entry_names[1]) { |is| is.read }) - AssertEntry::assert_contents(TEST_ZIP.entry_names[3], - zfRead.get_input_stream(TEST_ZIP.entry_names[3]) { |is| is.read }) - zfRead.close - end - - def test_replaceNonEntry - entryToReplace = "nonExistingEntryname" - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - assert_raise(Errno::ENOENT) { - zf.replace(entryToReplace, "data/file2.txt") - } - } - end - - def test_commit - newName = "renamedFirst" - zf = ZipFile.new(TEST_ZIP.zip_name) - oldName = zf.entries.first - zf.rename(oldName, newName) - zf.commit - - zfRead = ZipFile.new(TEST_ZIP.zip_name) - assert(zfRead.entries.detect { |e| e.name == newName } != nil) - assert(zfRead.entries.detect { |e| e.name == oldName } == nil) - zfRead.close - - zf.close - end - - def test_write_buffer - newName = "renamedFirst" - zf = ZipFile.new(TEST_ZIP.zip_name) - oldName = zf.entries.first - zf.rename(oldName, newName) - buffer = zf.write_buffer - File.open(TEST_ZIP.zip_name, 'wb') { |f| f.write buffer.string } - zfRead = ZipFile.new(TEST_ZIP.zip_name) - assert(zfRead.entries.detect { |e| e.name == newName } != nil) - assert(zfRead.entries.detect { |e| e.name == oldName } == nil) - zfRead.close - - zf.close - end - - # This test tests that after commit, you - # can delete the file you used to add the entry to the zip file - # with - def test_commitUseZipEntry - FileUtils.cp(TestFiles::RANDOM_ASCII_FILE1, "okToDelete.txt") - zf = ZipFile.open(TEST_ZIP.zip_name) - zf.add("okToDelete.txt", "okToDelete.txt") - assert_contains(zf, "okToDelete.txt") - zf.commit - File.rename("okToDelete.txt", "okToDeleteMoved.txt") - assert_contains(zf, "okToDelete.txt", "okToDeleteMoved.txt") - end - -# def test_close -# zf = ZipFile.new(TEST_ZIP.zip_name) -# zf.close -# assert_raise(IOError) { -# zf.extract(TEST_ZIP.entry_names.first, "hullubullu") -# } -# end - - def test_compound1 - renamedName = "renamedName" - originalEntries = [] - filename_to_remove = '' - begin - zf = ZipFile.new(TEST_ZIP.zip_name) - originalEntries = 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) - - TestFiles::BINARY_TEST_FILES.each { - |filename| - zf.add(filename, filename) - assert_contains(zf, filename) - } - - assert_contains(zf, originalEntries.last.to_s) - filename_to_remove = originalEntries.map(&:to_s).find {|name| name.match('longBinary')} - zf.remove(filename_to_remove) - assert_not_contains(zf, filename_to_remove) - - ensure - zf.close - end - begin - zfRead = ZipFile.new(TEST_ZIP.zip_name) - assert_contains(zfRead, TestFiles::RANDOM_ASCII_FILE1) - assert_contains(zfRead, renamedName) - TestFiles::BINARY_TEST_FILES.each { - |filename| - assert_contains(zfRead, filename) - } - assert_not_contains(zfRead, filename_to_remove) - ensure - zfRead.close - end - end - - def test_compound2 - begin - zf = ZipFile.new(TEST_ZIP.zip_name) - originalEntries = zf.entries.dup - - originalEntries.each { - |entry| - zf.remove(entry) - assert_not_contains(zf, entry) - } - assert(zf.entries.empty?) - - TestFiles::ASCII_TEST_FILES.each { - |filename| - zf.add(filename, filename) - assert_contains(zf, filename) - } - assert_equal(zf.entries.sort.map { |e| e.name }, TestFiles::ASCII_TEST_FILES) - - zf.rename(TestFiles::ASCII_TEST_FILES[0], "newName") - assert_not_contains(zf, TestFiles::ASCII_TEST_FILES[0]) - assert_contains(zf, "newName") - ensure - zf.close - end - begin - zfRead = ZipFile.new(TEST_ZIP.zip_name) - asciiTestFiles = TestFiles::ASCII_TEST_FILES.dup - asciiTestFiles.shift - asciiTestFiles.each { - |filename| - assert_contains(zf, filename) - } - - assert_contains(zf, "newName") - ensure - zfRead.close - end - end - - def test_changeComment - ZipFile.open(TEST_ZIP.zip_name) do |zf| - zf.comment = "my changed comment" - end - zfRead = ZipFile.open(TEST_ZIP.zip_name) - assert_equal("my changed comment", zfRead.comment) - end - - def test_preserve_file_order - entryNames = nil - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - entryNames = zf.entries.map{|e|e.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" - } - - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - assert_equal(entryNames, zf.entries.map{|e|e.to_s}) - entries = zf.entries.sort_by{|e|e.name}.reverse - entries.each { - |e| - zf.remove e - zf.get_output_stream(e){|os| os.write "foo"} - } - entryNames = entries.map{|e|e.to_s} - } - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - assert_equal(entryNames, zf.entries.map{|e|e.to_s}) - } - end - - 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}") - assert_entryContents(zf, entryName, filename) if File.exists?(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}") - end -end - -class ZipFileSplitTest < Test::Unit::TestCase - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zip_name = "large_zip_file.zip" - EXTRACTED_FILENAME = "extEntry" - UNSPLITTED_FILENAME = "unsplitted.zip" - ENTRY_TO_EXTRACT = TEST_ZIP.entry_names.first - - def setup - FileUtils.cp(TestZipFile::TEST_ZIP2.zip_name, TEST_ZIP.zip_name) - end - - def teardown - File.delete(TEST_ZIP.zip_name) - File.delete(UNSPLITTED_FILENAME) if File.exists?(UNSPLITTED_FILENAME) - - Dir["#{TEST_ZIP.zip_name}.*"].each do |zip_file_name| - File.delete(zip_file_name) if File.exists?(zip_file_name) - end - end - - def test_split_method_respond - assert_respond_to ZipFile, :split, "Does not have split class method" - end - - def test_split - result = ZipFile.split(TEST_ZIP.zip_name, 65536, false) - - unless 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([ZipFile::SPLIT_SIGNATURE].pack('V').size) if index == 0 - File.open(UNSPLITTED_FILENAME, 'ab') do |file| - file << zip_file.read - end - end - end - - ZipFile.open(UNSPLITTED_FILENAME) do |zf| - zf.extract(ENTRY_TO_EXTRACT, EXTRACTED_FILENAME) - - assert(File.exists?(EXTRACTED_FILENAME)) - AssertEntry::assert_contents(EXTRACTED_FILENAME, - zf.get_input_stream(ENTRY_TO_EXTRACT) { |is| is.read }) - - - File::unlink(EXTRACTED_FILENAME) - - entry = zf.get_entry(ENTRY_TO_EXTRACT) - entry.extract(EXTRACTED_FILENAME) - - assert(File.exists?(EXTRACTED_FILENAME)) - AssertEntry::assert_contents(EXTRACTED_FILENAME, - entry.get_input_stream() { |is| is.read }) - - end - end - end -end - -class ZipFileExtractTest < Test::Unit::TestCase - include CommonZipFileFixture - EXTRACTED_FILENAME = "extEntry" - ENTRY_TO_EXTRACT, *REMAINING_ENTRIES = TEST_ZIP.entry_names.reverse - - def setup - super - File.delete(EXTRACTED_FILENAME) if File.exists?(EXTRACTED_FILENAME) - end - - def test_extract - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - zf.extract(ENTRY_TO_EXTRACT, EXTRACTED_FILENAME) - - assert(File.exists?(EXTRACTED_FILENAME)) - AssertEntry::assert_contents(EXTRACTED_FILENAME, - zf.get_input_stream(ENTRY_TO_EXTRACT) { |is| is.read }) - - - File::unlink(EXTRACTED_FILENAME) - - entry = zf.get_entry(ENTRY_TO_EXTRACT) - entry.extract(EXTRACTED_FILENAME) - - assert(File.exists?(EXTRACTED_FILENAME)) - AssertEntry::assert_contents(EXTRACTED_FILENAME, - entry.get_input_stream() { |is| is.read }) - - } - end - - def test_extractExists - writtenText = "written text" - File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - - assert_raise(ZipDestinationFileExistsError) { - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) - } - } - File.open(EXTRACTED_FILENAME, "r") { - |f| - assert_equal(writtenText, f.read) - } - end - - def test_extractExistsOverwrite - writtenText = "written text" - File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - - gotCalledCorrectly = false - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) { - |entry, extractLoc| - gotCalledCorrectly = zf.entries.first == entry && - extractLoc == EXTRACTED_FILENAME - true - } - } - - assert(gotCalledCorrectly) - File.open(EXTRACTED_FILENAME, "r") { - |f| - assert(writtenText != f.read) - } - end - - def test_extractNonEntry - zf = ZipFile.new(TEST_ZIP.zip_name) - assert_raise(Errno::ENOENT) { zf.extract("nonExistingEntry", "nonExistingEntry") } - ensure - zf.close if zf - end - - def test_extractNonEntry2 - outFile = "outfile" - assert_raise(Errno::ENOENT) { - zf = ZipFile.new(TEST_ZIP.zip_name) - nonEntry = "hotdog-diddelidoo" - assert(! zf.entries.include?(nonEntry)) - zf.extract(nonEntry, outFile) - zf.close - } - assert(! File.exists?(outFile)) - end - -end - -class ZipFileExtractDirectoryTest < Test::Unit::TestCase - include CommonZipFileFixture - TEST_OUT_NAME = "emptyOutDir" - - def open_zip(&aProc) - assert(aProc != nil) - ZipFile.open(TestZipFile::TEST_ZIP4.zip_name, &aProc) - end - - def extract_test_dir(&aProc) - open_zip { - |zf| - zf.extract(TestFiles::EMPTY_TEST_DIR, TEST_OUT_NAME, &aProc) - } - end - - def setup - super - - Dir.rmdir(TEST_OUT_NAME) if File.directory? TEST_OUT_NAME - File.delete(TEST_OUT_NAME) if File.exists? TEST_OUT_NAME - end - - def test_extractDirectory - extract_test_dir - assert(File.directory?(TEST_OUT_NAME)) - end - - def test_extractDirectoryExistsAsDir - Dir.mkdir TEST_OUT_NAME - extract_test_dir - assert(File.directory?(TEST_OUT_NAME)) - end - - def test_extractDirectoryExistsAsFile - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - assert_raise(ZipDestinationFileExistsError) { extract_test_dir } - end - - def test_extractDirectoryExistsAsFileOverwrite - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - gotCalled = false - extract_test_dir { - |entry, destPath| - gotCalled = true - assert_equal(TEST_OUT_NAME, destPath) - assert(entry.is_directory) - true - } - assert(gotCalled) - assert(File.directory?(TEST_OUT_NAME)) - end -end - -class ZipExtraFieldTest < Test::Unit::TestCase - def test_new - extra_pure = ZipExtraField.new("") - extra_withstr = ZipExtraField.new("foo") - assert_instance_of(ZipExtraField, extra_pure) - assert_instance_of(ZipExtraField, extra_withstr) - end - - def test_unknownfield - extra = ZipExtraField.new("foo") - assert_equal(extra["Unknown"], "foo") - extra.merge("a") - assert_equal(extra["Unknown"], "fooa") - extra.merge("barbaz") - assert_equal(extra.to_s, "fooabarbaz") - end - - - def test_merge - str = "UT\x5\0\x3\250$\r@Ux\0\0" - extra1 = ZipExtraField.new("") - extra2 = ZipExtraField.new(str) - assert(! extra1.member?("UniversalTime")) - assert(extra2.member?("UniversalTime")) - extra1.merge(str) - assert_equal(extra1["UniversalTime"].mtime, extra2["UniversalTime"].mtime) - end - - def test_length - str = "UT\x5\0\x3\250$\r@Ux\0\0Te\0\0testit" - extra = ZipExtraField.new(str) - assert_equal(extra.local_length, extra.to_local_bin.length) - assert_equal(extra.c_dir_length, extra.to_c_dir_bin.length) - extra.merge("foo") - assert_equal(extra.local_length, extra.to_local_bin.length) - assert_equal(extra.c_dir_length, extra.to_c_dir_bin.length) - end - - - def test_to_s - str = "UT\x5\0\x3\250$\r@Ux\0\0Te\0\0testit" - extra = ZipExtraField.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) - end - - def test_equality - str = "UT\x5\0\x3\250$\r@" - extra1 = ZipExtraField.new(str) - extra2 = ZipExtraField.new(str) - extra3 = ZipExtraField.new(str) - assert_equal(extra1, extra2) - - extra2["UniversalTime"].mtime = DOSTime.now - assert(extra1 != extra2) - - extra3.create("IUnix") - assert(extra1 != extra3) - - extra1.create("IUnix") - assert_equal(extra1, extra3) - end - -end - -class ZipUnicodeFileNamesAndComments < Test::Unit::TestCase - - FILENAME = File.join(File.dirname(__FILE__), "test1.zip") - - def test_unicode - file_entrys = ["текстовыйфайл.txt", "Résumé.txt", "슬레이어스휘.txt"] - directory_entrys = ["папка/текстовыйфайл.txt", "Résumé/Résumé.txt", "슬레이어스휘/슬레이어스휘.txt"] - stream = ::Zip::ZipOutputStream.open(FILENAME) do |io| - file_entrys.each do |filename| - io.put_next_entry(filename) - io.write(filename) - end - directory_entrys.each do |filepath| - io.put_next_entry(filepath) - io.write(filepath) - end - end - assert(!stream.nil?) - ::Zip::ZipInputStream.open(FILENAME) do |io| - file_entrys.each do |filename| - entry = io.get_next_entry - entry_name = entry.name - entry_name = entry_name.force_encoding("UTF-8") if RUBY_VERSION >= '1.9' - assert(filename == entry_name) - end - directory_entrys.each do |filepath| - entry = io.get_next_entry - entry_name = entry.name - entry_name = entry_name.force_encoding("UTF-8") if RUBY_VERSION >= '1.9' - assert(filepath == entry_name) - end - end - ::File.unlink(FILENAME) - end - -end - -class ZipSettingsTest < Test::Unit::TestCase - # TODO Refactor out into common test module - include CommonZipFileFixture - TEST_OUT_NAME = "emptyOutDir" - - def setup - super - - Dir.rmdir(TEST_OUT_NAME) if File.directory? TEST_OUT_NAME - File.delete(TEST_OUT_NAME) if File.exists? TEST_OUT_NAME - end - - def open_zip(&aProc) - assert(aProc != nil) - ZipFile.open(TestZipFile::TEST_ZIP4.zip_name, &aProc) - end - - def extract_test_dir(&aProc) - open_zip { - |zf| - zf.extract(TestFiles::EMPTY_TEST_DIR, TEST_OUT_NAME, &aProc) - } - end - - def test_true_on_exists_proc - Zip.options[:on_exists_proc] = true - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - extract_test_dir - assert(File.directory?(TEST_OUT_NAME)) - end - - def test_false_on_exists_proc - Zip.options[:on_exists_proc] = false - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - assert_raise(Zip::ZipDestinationFileExistsError) { extract_test_dir } - end - - def test_false_continue_on_exists_proc - Zip.options[:continue_on_exists_proc] = false - - assert_raise(ZipEntryExistsError) do - ZipFile.open(TEST_ZIP.zip_name) do |zf| - zf.add(zf.entries.first.name, "data/file2.txt") - end - end - end - - def test_true_continue_on_exists_proc - Zip.options[:continue_on_exists_proc] = true - - replacedEntry = nil - - ZipFile.open(TEST_ZIP.zip_name) do |zf| - replacedEntry = zf.entries.first.name - zf.add(replacedEntry, "data/file2.txt") - end - - ZipFile.open(TEST_ZIP.zip_name) do |zf| - assert_contains(zf, replacedEntry, "data/file2.txt") - end - end - - 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}") - assert_entryContents(zf, entryName, filename) if File.exists?(filename) - end -end - - -# Copyright (C) 2002-2005 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/testAllRubies.sh b/testAllRubies.sh deleted file mode 100755 index a23177fc..00000000 --- a/testAllRubies.sh +++ /dev/null @@ -1,4 +0,0 @@ -for i in ree 1.8.7 1.9.2 1.9.3; do - git clean -nfx - rvm $i do rake -done