From f37ce795a17bc504dd3db71c9cf6dd4c41f1d7e0 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 10 Jun 2014 13:39:05 -0500 Subject: [PATCH] Remove lib/zip MSP-10004 --- lib/zip.rb | 1 - lib/zip/ChangeLog | 1146 ----- lib/zip/NEWS | 162 - lib/zip/README | 72 - lib/zip/TODO | 16 - lib/zip/ioextras.rb | 165 - lib/zip/samples/example.rb | 69 - lib/zip/samples/example_filesystem.rb | 33 - lib/zip/samples/gtkRubyzip.rb | 86 - lib/zip/samples/qtzip.rb | 101 - lib/zip/samples/write_simple.rb | 13 - lib/zip/samples/zipfind.rb | 74 - lib/zip/stdrubyext.rb | 111 - lib/zip/tempfile_bugfixed.rb | 195 - lib/zip/test/alltests.rb | 9 - lib/zip/test/data/file1.txt | 46 - lib/zip/test/data/file1.txt.deflatedData | Bin 482 -> 0 bytes lib/zip/test/data/file2.txt | 1504 ------ lib/zip/test/data/generated/5entry.zip | Bin 54114 -> 0 bytes lib/zip/test/data/generated/empty.txt | 0 lib/zip/test/data/generated/empty.zip | Bin 22 -> 0 bytes .../test/data/generated/empty_chmod640.txt | 0 .../test/data/generated/emptytestdir/.keep | 0 lib/zip/test/data/generated/longAscii.txt | 4512 ----------------- lib/zip/test/data/generated/longBinary.bin | Bin 300078 -> 0 bytes lib/zip/test/data/generated/randomAscii1.txt | 1 - lib/zip/test/data/generated/randomAscii2.txt | 1 - lib/zip/test/data/generated/randomAscii3.txt | 1 - lib/zip/test/data/generated/randomBinary1.bin | Bin 4 -> 0 bytes lib/zip/test/data/generated/randomBinary2.bin | Bin 4 -> 0 bytes lib/zip/test/data/generated/short.txt | 1 - lib/zip/test/data/generated/test1.zip | Bin 646 -> 0 bytes lib/zip/test/data/generated/zipWithDir.zip | Bin 830 -> 0 bytes lib/zip/test/data/notzippedruby.rb | 7 - lib/zip/test/data/rubycode.zip | Bin 617 -> 0 bytes lib/zip/test/data/rubycode2.zip | Bin 261 -> 0 bytes lib/zip/test/data/testDirectory.bin | Bin 303 -> 0 bytes lib/zip/test/data/zipWithDirs.zip | Bin 1934 -> 0 bytes lib/zip/test/gentestfiles.rb | 160 - lib/zip/test/ioextrastest.rb | 208 - lib/zip/test/stdrubyexttest.rb | 52 - lib/zip/test/zipfilesystemtest.rb | 845 --- lib/zip/test/ziprequiretest.rb | 43 - lib/zip/test/ziptest.rb | 1623 ------ lib/zip/zip.rb | 1900 ------- lib/zip/zipfilesystem.rb | 611 --- lib/zip/ziprequire.rb | 90 - 47 files changed, 13858 deletions(-) delete mode 100644 lib/zip.rb delete mode 100644 lib/zip/ChangeLog delete mode 100644 lib/zip/NEWS delete mode 100644 lib/zip/README delete mode 100644 lib/zip/TODO delete mode 100644 lib/zip/ioextras.rb delete mode 100755 lib/zip/samples/example.rb delete mode 100755 lib/zip/samples/example_filesystem.rb delete mode 100755 lib/zip/samples/gtkRubyzip.rb delete mode 100755 lib/zip/samples/qtzip.rb delete mode 100755 lib/zip/samples/write_simple.rb delete mode 100755 lib/zip/samples/zipfind.rb delete mode 100644 lib/zip/stdrubyext.rb delete mode 100644 lib/zip/tempfile_bugfixed.rb delete mode 100755 lib/zip/test/alltests.rb delete mode 100644 lib/zip/test/data/file1.txt delete mode 100644 lib/zip/test/data/file1.txt.deflatedData delete mode 100644 lib/zip/test/data/file2.txt delete mode 100644 lib/zip/test/data/generated/5entry.zip delete mode 100644 lib/zip/test/data/generated/empty.txt delete mode 100644 lib/zip/test/data/generated/empty.zip delete mode 100644 lib/zip/test/data/generated/empty_chmod640.txt delete mode 100644 lib/zip/test/data/generated/emptytestdir/.keep delete mode 100644 lib/zip/test/data/generated/longAscii.txt delete mode 100644 lib/zip/test/data/generated/longBinary.bin delete mode 100644 lib/zip/test/data/generated/randomAscii1.txt delete mode 100644 lib/zip/test/data/generated/randomAscii2.txt delete mode 100644 lib/zip/test/data/generated/randomAscii3.txt delete mode 100644 lib/zip/test/data/generated/randomBinary1.bin delete mode 100644 lib/zip/test/data/generated/randomBinary2.bin delete mode 100644 lib/zip/test/data/generated/short.txt delete mode 100644 lib/zip/test/data/generated/test1.zip delete mode 100644 lib/zip/test/data/generated/zipWithDir.zip delete mode 100755 lib/zip/test/data/notzippedruby.rb delete mode 100644 lib/zip/test/data/rubycode.zip delete mode 100644 lib/zip/test/data/rubycode2.zip delete mode 100644 lib/zip/test/data/testDirectory.bin delete mode 100644 lib/zip/test/data/zipWithDirs.zip delete mode 100755 lib/zip/test/gentestfiles.rb delete mode 100755 lib/zip/test/ioextrastest.rb delete mode 100755 lib/zip/test/stdrubyexttest.rb delete mode 100755 lib/zip/test/zipfilesystemtest.rb delete mode 100755 lib/zip/test/ziprequiretest.rb delete mode 100755 lib/zip/test/ziptest.rb delete mode 100644 lib/zip/zip.rb delete mode 100644 lib/zip/zipfilesystem.rb delete mode 100644 lib/zip/ziprequire.rb 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 bfbb4f42c009154e9e3304855c31813275b709d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 482 zcmV<80UiE@RMBpmFcf{>{fa9!51TglfJv3cnzTZrO$4cwhiS+$rdV}s6dQHj*U#Vt z3=5f`xX0%l?mad@^t@d^Mn6{hdb5q!PZ{3gi);W^yKNff%Q)Lw#4v5bKfDIG+wJa? z=pnns-~~V`F15*%_4Ca zj^EboH)XZqO6tya0#)-~Treih@%_}yQ1_j5gZaJAdUeEVT>IuDsQSN`rbJ4Y7;mF@ zf!i26zX>!yBbTKhhP8Aj^y*W$=fmwAo%K2sJ)!HNmwM3k8J!jDh3C2&Q7T4?A^j^} z9r3Ik8+;@B3zEjD19@fmrW#RnN-n8r3f5ArlwiSXCJQF% zdpJoZSw_p{^uI6; YT71LBFMz*PQb9>fNlr&oR8>Ys3VC(y$N&HU 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 ef580052eda47f14c1b7e22abff82188c5e66386..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54114 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 F{|Cm93OWD) 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 15cb0ecb3e219d1701294bfdf0fe3f5cb5d208e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22 NcmWIWW@Tf*000g10H*)| 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 df673254c41319ae12b39a976892ce8292d288cd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 300078 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 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 593f4708db84ac8fd0f5cc47c634f38c013fe9e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4 LcmZQzU|;|M00aO5 diff --git a/lib/zip/test/data/generated/randomBinary2.bin b/lib/zip/test/data/generated/randomBinary2.bin deleted file mode 100644 index 593f4708db84ac8fd0f5cc47c634f38c013fe9e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4 LcmZQzU|;|M00aO5 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 2ce05cedf4447ad8f301d2e157d699fdd9a85ad6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 646 zcmWIWW@Zs#U|`^2kTH<3-1D|^`4vV627Oir20jKEhLpsTME$hPoK!=-l8Tbh5Kac> zbo-4SV$6a*r4`%^j4Ush85qDsYskgC*#;uD_x^TioAS>#`_bSz_1LT}PK$k6R!(Wp zJYl#jqBKWDy7cb#^AA{fxM$Aaas0W=J6kh{FMDF`-R#4DR&DOR?k{=oQFjhQ*zR1Z zr>8A$M;$T9J!HR!ZO84m?@x?YrMK-d;Y5+c@NwjHKm(UT3d^TOAwj8LC%qtMhxm>-!td+PmhdC;Jjx z%ch2OPPk^ux4K!2{|9G9;npK_RvmUc*?0fi8}rny54~QUD`g7(92aAtcJ|}nD`x_} z&n;>Gx%XUE>5;6M-_Mj+{MtW#nTywSiTuUojkhJN_j8)qD^Fjl(6+Ti{mZN1?avRt zm7e?6O4xSc>xI*+E_{`oEBd8J> zm33ZnS9z;DZB&XEMdzH_JVE)+gULp7j7&rg|Fw6M&Q9K1fhws-%P_|Y)(tzF^=$>y`QN#@4eI~${F4j zbo-4SV$6a*r4`%^j4Ush85qDsYskgC*#;uD_x^TioAS>#`_bSz_1LT}PK$k6R!(Wp zJYl#jqBKWDy7cb#^AA{fxM$Aaas0W=J6kh{FMDF`-R#4DR&DOR?k{=oQFjhQ*zR1Z zr>8A$M;$T9J!HR!ZO84m?@x?YrMK-d;Y5+c@NwjHKm(UT3d^TOAwj8LC%qtMhxm>-!td+PmhdC;Jjx z%ch2OPPk^ux4K!2{|9G9;npK_RvmUc*?0fi8}rny54~QUD`g7(92aAtcJ|}nD`x_} z&n;>Gx%XUE>5;6M-_Mj+{MtW#nTywSiTuUojkhJN_j8)qD^Fjl(6+Ti{mZN1?avRt zm7e?6O4xSc>xI*+E_{`oEBd8J> zm33ZnS9z;DZB&XEMdzH_JVE)+gULp7j7&rg|Fw6M&Q9K1fhws-%P_|Y)(tzF^=$>y`QN#@4eI~${F4joB*?I&5yV7Kfvk`eh}BG7ZiSf1z#zcz)=`EDY9>yX18uXQ6aq^YrF$udom<7Oc6lM6}=RDLT(4ef%v0bc!4h^8y1W zF(|b-zqBYhRj;I?1ROS-fZ>81HlI)HpFDBKJKSTf$2lEFL!-|k4N6D3GG|(@>;ig~ zkx85xSJ3kU?ONSo@jDa*vNRhlwoTaWDdGpXl_l7YYQ5=Tcy zrKJm6mDneFo%MeBlu4K&z?+dtoEeupd4aBFU| zA4oGE-6BAHIKZ|xRwx*G19_o9EQG2%Ei)(8(9jU>1duaeP7pnEkh8&nhxJ1B@*nf6 zHmC3$*O)cQq4xsI(%Y*)R4Bgs(PD5T$>)hP`&4@^Uu)sOHmP%+3H(JZ*Tj$A;8upFq4-#D)-_!rOo zz!GmJQzzjm|Ki#?1CQIcEI2yj#PYsPrf+proq4ajTwpGhS;Rkm%D0A)zZ8EJcxCc?6yCYU#-$Z+V)|>29qyQgrU)ZKDMG0hH6=mA&`2McB8;FZB0rGW z^aM>2Fx?OquacTrK4QqJHwn1X`eeKbzSH2tf ze~@$y7Fr=VYr&WAVTm&&(+zp%a4xxL^+-`ycmB2e%C1^V4FwljSkE~#@%Fi*%OZ!K zJ;>Ryr7T6HD!}#Rvky$Dk$dY_Uj 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.