rubyzip 0.9.9 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +284 -41
  3. data/Rakefile +11 -6
  4. data/TODO +0 -1
  5. data/lib/zip/central_directory.rb +208 -0
  6. data/lib/zip/compressor.rb +1 -2
  7. data/lib/zip/constants.rb +59 -7
  8. data/lib/zip/crypto/encryption.rb +11 -0
  9. data/lib/zip/crypto/null_encryption.rb +43 -0
  10. data/lib/zip/crypto/traditional_encryption.rb +99 -0
  11. data/lib/zip/decompressor.rb +4 -4
  12. data/lib/zip/deflater.rb +17 -13
  13. data/lib/zip/dos_time.rb +13 -14
  14. data/lib/zip/entry.rb +700 -0
  15. data/lib/zip/entry_set.rb +86 -0
  16. data/lib/zip/errors.rb +18 -0
  17. data/lib/zip/extra_field/generic.rb +43 -0
  18. data/lib/zip/extra_field/ntfs.rb +90 -0
  19. data/lib/zip/extra_field/old_unix.rb +44 -0
  20. data/lib/zip/extra_field/universal_time.rb +47 -0
  21. data/lib/zip/extra_field/unix.rb +37 -0
  22. data/lib/zip/extra_field/zip64.rb +68 -0
  23. data/lib/zip/extra_field/zip64_placeholder.rb +15 -0
  24. data/lib/zip/extra_field.rb +101 -0
  25. data/lib/zip/file.rb +443 -0
  26. data/lib/zip/{zipfilesystem.rb → filesystem.rb} +162 -157
  27. data/lib/zip/inflater.rb +29 -28
  28. data/lib/zip/input_stream.rb +173 -0
  29. data/lib/zip/ioextras/abstract_input_stream.rb +111 -0
  30. data/lib/zip/ioextras/abstract_output_stream.rb +43 -0
  31. data/lib/zip/ioextras.rb +21 -149
  32. data/lib/zip/null_compressor.rb +2 -2
  33. data/lib/zip/null_decompressor.rb +8 -6
  34. data/lib/zip/null_input_stream.rb +3 -2
  35. data/lib/zip/output_stream.rb +189 -0
  36. data/lib/zip/pass_thru_compressor.rb +6 -6
  37. data/lib/zip/pass_thru_decompressor.rb +19 -19
  38. data/lib/zip/{zip_streamable_directory.rb → streamable_directory.rb} +3 -3
  39. data/lib/zip/streamable_stream.rb +56 -0
  40. data/lib/zip/version.rb +3 -0
  41. data/lib/zip.rb +71 -0
  42. data/samples/example.rb +44 -32
  43. data/samples/example_filesystem.rb +16 -18
  44. data/samples/example_recursive.rb +33 -28
  45. data/samples/{gtkRubyzip.rb → gtk_ruby_zip.rb} +26 -28
  46. data/samples/qtzip.rb +22 -31
  47. data/samples/write_simple.rb +12 -13
  48. data/samples/zipfind.rb +31 -39
  49. data/test/basic_zip_file_test.rb +60 -0
  50. data/test/case_sensitivity_test.rb +69 -0
  51. data/test/central_directory_entry_test.rb +69 -0
  52. data/test/central_directory_test.rb +100 -0
  53. data/test/crypto/null_encryption_test.rb +57 -0
  54. data/test/crypto/traditional_encryption_test.rb +80 -0
  55. data/test/data/WarnInvalidDate.zip +0 -0
  56. data/test/data/file1.txt +46 -0
  57. data/test/data/file1.txt.deflatedData +0 -0
  58. data/test/data/file2.txt +1504 -0
  59. data/test/data/globTest/foo/bar/baz/foo.txt +0 -0
  60. data/test/data/globTest/foo.txt +0 -0
  61. data/test/data/globTest/food.txt +0 -0
  62. data/test/data/globTest.zip +0 -0
  63. data/test/data/gpbit3stored.zip +0 -0
  64. data/test/data/mimetype +1 -0
  65. data/test/data/notzippedruby.rb +7 -0
  66. data/test/data/ntfs.zip +0 -0
  67. data/test/data/oddExtraField.zip +0 -0
  68. data/test/data/path_traversal/Makefile +10 -0
  69. data/test/data/path_traversal/jwilk/README.md +5 -0
  70. data/test/data/path_traversal/jwilk/absolute1.zip +0 -0
  71. data/test/data/path_traversal/jwilk/absolute2.zip +0 -0
  72. data/test/data/path_traversal/jwilk/dirsymlink.zip +0 -0
  73. data/test/data/path_traversal/jwilk/dirsymlink2a.zip +0 -0
  74. data/test/data/path_traversal/jwilk/dirsymlink2b.zip +0 -0
  75. data/test/data/path_traversal/jwilk/relative0.zip +0 -0
  76. data/test/data/path_traversal/jwilk/relative2.zip +0 -0
  77. data/test/data/path_traversal/jwilk/symlink.zip +0 -0
  78. data/test/data/path_traversal/relative1.zip +0 -0
  79. data/test/data/path_traversal/tilde.zip +0 -0
  80. data/test/data/path_traversal/tuzovakaoff/README.md +3 -0
  81. data/test/data/path_traversal/tuzovakaoff/absolutepath.zip +0 -0
  82. data/test/data/path_traversal/tuzovakaoff/symlink.zip +0 -0
  83. data/test/data/rubycode.zip +0 -0
  84. data/test/data/rubycode2.zip +0 -0
  85. data/test/data/test.xls +0 -0
  86. data/test/data/testDirectory.bin +0 -0
  87. data/test/data/zip64-sample.zip +0 -0
  88. data/test/data/zipWithDirs.zip +0 -0
  89. data/test/data/zipWithEncryption.zip +0 -0
  90. data/test/deflater_test.rb +65 -0
  91. data/test/encryption_test.rb +42 -0
  92. data/test/entry_set_test.rb +163 -0
  93. data/test/entry_test.rb +154 -0
  94. data/test/errors_test.rb +35 -0
  95. data/test/extra_field_test.rb +76 -0
  96. data/test/file_extract_directory_test.rb +54 -0
  97. data/test/file_extract_test.rb +145 -0
  98. data/test/file_permissions_test.rb +65 -0
  99. data/test/file_split_test.rb +57 -0
  100. data/test/file_test.rb +666 -0
  101. data/test/filesystem/dir_iterator_test.rb +58 -0
  102. data/test/filesystem/directory_test.rb +139 -0
  103. data/test/filesystem/file_mutating_test.rb +87 -0
  104. data/test/filesystem/file_nonmutating_test.rb +508 -0
  105. data/test/filesystem/file_stat_test.rb +64 -0
  106. data/test/gentestfiles.rb +126 -0
  107. data/test/inflater_test.rb +14 -0
  108. data/test/input_stream_test.rb +182 -0
  109. data/test/ioextras/abstract_input_stream_test.rb +102 -0
  110. data/test/ioextras/abstract_output_stream_test.rb +106 -0
  111. data/test/ioextras/fake_io_test.rb +18 -0
  112. data/test/local_entry_test.rb +154 -0
  113. data/test/output_stream_test.rb +128 -0
  114. data/test/pass_thru_compressor_test.rb +30 -0
  115. data/test/pass_thru_decompressor_test.rb +14 -0
  116. data/test/path_traversal_test.rb +141 -0
  117. data/test/samples/example_recursive_test.rb +37 -0
  118. data/test/settings_test.rb +95 -0
  119. data/test/test_helper.rb +234 -0
  120. data/test/unicode_file_names_and_comments_test.rb +62 -0
  121. data/test/zip64_full_test.rb +51 -0
  122. data/test/zip64_support_test.rb +14 -0
  123. metadata +274 -41
  124. data/NEWS +0 -172
  125. data/lib/zip/settings.rb +0 -10
  126. data/lib/zip/tempfile_bugfixed.rb +0 -195
  127. data/lib/zip/zip.rb +0 -56
  128. data/lib/zip/zip_central_directory.rb +0 -135
  129. data/lib/zip/zip_entry.rb +0 -638
  130. data/lib/zip/zip_entry_set.rb +0 -77
  131. data/lib/zip/zip_extra_field.rb +0 -213
  132. data/lib/zip/zip_file.rb +0 -340
  133. data/lib/zip/zip_input_stream.rb +0 -144
  134. data/lib/zip/zip_output_stream.rb +0 -173
  135. data/lib/zip/zip_streamable_stream.rb +0 -47
@@ -0,0 +1,54 @@
1
+ require 'test_helper'
2
+
3
+ class ZipFileExtractDirectoryTest < MiniTest::Test
4
+ include CommonZipFileFixture
5
+
6
+ TEST_OUT_NAME = 'test/data/generated/emptyOutDir'
7
+
8
+ def open_zip(&aProc)
9
+ assert(!aProc.nil?)
10
+ ::Zip::File.open(TestZipFile::TEST_ZIP4.zip_name, &aProc)
11
+ end
12
+
13
+ def extract_test_dir(&aProc)
14
+ open_zip do |zf|
15
+ zf.extract(TestFiles::EMPTY_TEST_DIR, TEST_OUT_NAME, &aProc)
16
+ end
17
+ end
18
+
19
+ def setup
20
+ super
21
+
22
+ Dir.rmdir(TEST_OUT_NAME) if File.directory? TEST_OUT_NAME
23
+ File.delete(TEST_OUT_NAME) if File.exist? TEST_OUT_NAME
24
+ end
25
+
26
+ def test_extract_directory
27
+ extract_test_dir
28
+ assert(File.directory?(TEST_OUT_NAME))
29
+ end
30
+
31
+ def test_extract_directory_exists_as_dir
32
+ Dir.mkdir TEST_OUT_NAME
33
+ extract_test_dir
34
+ assert(File.directory?(TEST_OUT_NAME))
35
+ end
36
+
37
+ def test_extract_directory_exists_as_file
38
+ File.open(TEST_OUT_NAME, 'w') { |f| f.puts 'something' }
39
+ assert_raises(::Zip::DestinationFileExistsError) { extract_test_dir }
40
+ end
41
+
42
+ def test_extract_directory_exists_as_file_overwrite
43
+ File.open(TEST_OUT_NAME, 'w') { |f| f.puts 'something' }
44
+ gotCalled = false
45
+ extract_test_dir do |entry, destPath|
46
+ gotCalled = true
47
+ assert_equal(TEST_OUT_NAME, destPath)
48
+ assert(entry.directory?)
49
+ true
50
+ end
51
+ assert(gotCalled)
52
+ assert(File.directory?(TEST_OUT_NAME))
53
+ end
54
+ end
@@ -0,0 +1,145 @@
1
+ require 'test_helper'
2
+
3
+ class ZipFileExtractTest < MiniTest::Test
4
+ include CommonZipFileFixture
5
+ EXTRACTED_FILENAME = 'test/data/generated/extEntry'
6
+ ENTRY_TO_EXTRACT, *REMAINING_ENTRIES = TEST_ZIP.entry_names.reverse
7
+
8
+ def setup
9
+ super
10
+ ::File.delete(EXTRACTED_FILENAME) if ::File.exist?(EXTRACTED_FILENAME)
11
+ end
12
+
13
+ def teardown
14
+ ::Zip.reset!
15
+ end
16
+
17
+ def test_extract
18
+ ::Zip::File.open(TEST_ZIP.zip_name) do |zf|
19
+ zf.extract(ENTRY_TO_EXTRACT, EXTRACTED_FILENAME)
20
+
21
+ assert(File.exist?(EXTRACTED_FILENAME))
22
+ AssertEntry.assert_contents(EXTRACTED_FILENAME,
23
+ zf.get_input_stream(ENTRY_TO_EXTRACT) { |is| is.read })
24
+
25
+ ::File.unlink(EXTRACTED_FILENAME)
26
+
27
+ entry = zf.get_entry(ENTRY_TO_EXTRACT)
28
+ entry.extract(EXTRACTED_FILENAME)
29
+
30
+ assert(File.exist?(EXTRACTED_FILENAME))
31
+ AssertEntry.assert_contents(EXTRACTED_FILENAME,
32
+ entry.get_input_stream { |is| is.read })
33
+ end
34
+ end
35
+
36
+ def test_extract_exists
37
+ writtenText = 'written text'
38
+ ::File.open(EXTRACTED_FILENAME, 'w') { |f| f.write(writtenText) }
39
+
40
+ assert_raises(::Zip::DestinationFileExistsError) do
41
+ ::Zip::File.open(TEST_ZIP.zip_name) do |zf|
42
+ zf.extract(zf.entries.first, EXTRACTED_FILENAME)
43
+ end
44
+ end
45
+ File.open(EXTRACTED_FILENAME, 'r') do |f|
46
+ assert_equal(writtenText, f.read)
47
+ end
48
+ end
49
+
50
+ def test_extract_exists_overwrite
51
+ writtenText = 'written text'
52
+ ::File.open(EXTRACTED_FILENAME, 'w') { |f| f.write(writtenText) }
53
+
54
+ gotCalledCorrectly = false
55
+ ::Zip::File.open(TEST_ZIP.zip_name) do |zf|
56
+ zf.extract(zf.entries.first, EXTRACTED_FILENAME) do |entry, extractLoc|
57
+ gotCalledCorrectly = zf.entries.first == entry &&
58
+ extractLoc == EXTRACTED_FILENAME
59
+ true
60
+ end
61
+ end
62
+
63
+ assert(gotCalledCorrectly)
64
+ ::File.open(EXTRACTED_FILENAME, 'r') do |f|
65
+ assert(writtenText != f.read)
66
+ end
67
+ end
68
+
69
+ def test_extract_non_entry
70
+ zf = ::Zip::File.new(TEST_ZIP.zip_name)
71
+ assert_raises(Errno::ENOENT) { zf.extract('nonExistingEntry', 'nonExistingEntry') }
72
+ ensure
73
+ zf.close if zf
74
+ end
75
+
76
+ def test_extract_non_entry_2
77
+ outFile = 'outfile'
78
+ assert_raises(Errno::ENOENT) do
79
+ zf = ::Zip::File.new(TEST_ZIP.zip_name)
80
+ nonEntry = 'hotdog-diddelidoo'
81
+ assert(!zf.entries.include?(nonEntry))
82
+ zf.extract(nonEntry, outFile)
83
+ zf.close
84
+ end
85
+ assert(!File.exist?(outFile))
86
+ end
87
+
88
+ def test_extract_incorrect_size
89
+ # The uncompressed size fields in the zip file cannot be trusted. This makes
90
+ # it harder for callers to validate the sizes of the files they are
91
+ # extracting, which can lead to denial of service. See also
92
+ # https://en.wikipedia.org/wiki/Zip_bomb
93
+ Dir.mktmpdir do |tmp|
94
+ real_zip = File.join(tmp, 'real.zip')
95
+ fake_zip = File.join(tmp, 'fake.zip')
96
+ file_name = 'a'
97
+ true_size = 500_000
98
+ fake_size = 1
99
+
100
+ ::Zip::File.open(real_zip, ::Zip::File::CREATE) do |zf|
101
+ zf.get_output_stream(file_name) do |os|
102
+ os.write 'a' * true_size
103
+ end
104
+ end
105
+
106
+ compressed_size = nil
107
+ ::Zip::File.open(real_zip) do |zf|
108
+ a_entry = zf.find_entry(file_name)
109
+ compressed_size = a_entry.compressed_size
110
+ assert_equal true_size, a_entry.size
111
+ end
112
+
113
+ true_size_bytes = [compressed_size, true_size, file_name.size].pack('LLS')
114
+ fake_size_bytes = [compressed_size, fake_size, file_name.size].pack('LLS')
115
+
116
+ data = File.binread(real_zip)
117
+ assert data.include?(true_size_bytes)
118
+ data.gsub! true_size_bytes, fake_size_bytes
119
+
120
+ File.open(fake_zip, 'wb') do |file|
121
+ file.write data
122
+ end
123
+
124
+ Dir.chdir tmp do
125
+ ::Zip::File.open(fake_zip) do |zf|
126
+ a_entry = zf.find_entry(file_name)
127
+ assert_equal fake_size, a_entry.size
128
+
129
+ ::Zip.validate_entry_sizes = false
130
+ a_entry.extract
131
+ assert_equal true_size, File.size(file_name)
132
+ FileUtils.rm file_name
133
+
134
+ ::Zip.validate_entry_sizes = true
135
+ error = assert_raises ::Zip::EntrySizeError do
136
+ a_entry.extract
137
+ end
138
+ assert_equal \
139
+ 'Entry a should be 1B but is larger when inflated',
140
+ error.message
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,65 @@
1
+ require 'test_helper'
2
+
3
+ class FilePermissionsTest < MiniTest::Test
4
+ ZIPNAME = File.join(File.dirname(__FILE__), 'umask.zip')
5
+ FILENAME = File.join(File.dirname(__FILE__), 'umask.txt')
6
+
7
+ def teardown
8
+ ::File.unlink(ZIPNAME)
9
+ ::File.unlink(FILENAME)
10
+ end
11
+
12
+ def test_current_umask
13
+ create_files
14
+ assert_matching_permissions FILENAME, ZIPNAME
15
+ end
16
+
17
+ def test_umask_000
18
+ set_umask(0o000) do
19
+ create_files
20
+ end
21
+
22
+ assert_matching_permissions FILENAME, ZIPNAME
23
+ end
24
+
25
+ def test_umask_066
26
+ set_umask(0o066) do
27
+ create_files
28
+ end
29
+
30
+ assert_matching_permissions FILENAME, ZIPNAME
31
+ end
32
+
33
+ def test_umask_027
34
+ set_umask(0o027) do
35
+ create_files
36
+ end
37
+
38
+ assert_matching_permissions FILENAME, ZIPNAME
39
+ end
40
+
41
+ def assert_matching_permissions(expected_file, actual_file)
42
+ assert_equal(
43
+ ::File.stat(expected_file).mode.to_s(8).rjust(4, '0'),
44
+ ::File.stat(actual_file).mode.to_s(8).rjust(4, '0')
45
+ )
46
+ end
47
+
48
+ def create_files
49
+ ::Zip::File.open(ZIPNAME, ::Zip::File::CREATE) do |zip|
50
+ zip.comment = 'test'
51
+ end
52
+
53
+ ::File.open(FILENAME, 'w') do |file|
54
+ file << 'test'
55
+ end
56
+ end
57
+
58
+ # If anything goes wrong, make sure the umask is restored.
59
+ def set_umask(umask)
60
+ saved_umask = ::File.umask(umask)
61
+ yield
62
+ ensure
63
+ ::File.umask(saved_umask)
64
+ end
65
+ end
@@ -0,0 +1,57 @@
1
+ require 'test_helper'
2
+
3
+ class ZipFileSplitTest < MiniTest::Test
4
+ TEST_ZIP = TestZipFile::TEST_ZIP2.clone
5
+ TEST_ZIP.zip_name = 'large_zip_file.zip'
6
+ EXTRACTED_FILENAME = 'test/data/generated/extEntrySplit'
7
+ UNSPLITTED_FILENAME = 'test/data/generated/unsplitted.zip'
8
+ ENTRY_TO_EXTRACT = TEST_ZIP.entry_names.first
9
+
10
+ def setup
11
+ FileUtils.cp(TestZipFile::TEST_ZIP2.zip_name, TEST_ZIP.zip_name)
12
+ end
13
+
14
+ def teardown
15
+ File.delete(TEST_ZIP.zip_name)
16
+ File.delete(UNSPLITTED_FILENAME) if File.exist?(UNSPLITTED_FILENAME)
17
+
18
+ Dir["#{TEST_ZIP.zip_name}.*"].each do |zip_file_name|
19
+ File.delete(zip_file_name) if File.exist?(zip_file_name)
20
+ end
21
+ end
22
+
23
+ def test_split_method_respond
24
+ assert_respond_to ::Zip::File, :split, 'Does not have split class method'
25
+ end
26
+
27
+ def test_split
28
+ result = ::Zip::File.split(TEST_ZIP.zip_name, 65_536, false)
29
+
30
+ return if result.nil?
31
+ Dir["#{TEST_ZIP.zip_name}.*"].sort.each_with_index do |zip_file_name, index|
32
+ File.open(zip_file_name, 'rb') do |zip_file|
33
+ zip_file.read([::Zip::File::SPLIT_SIGNATURE].pack('V').size) if index == 0
34
+ File.open(UNSPLITTED_FILENAME, 'ab') do |file|
35
+ file << zip_file.read
36
+ end
37
+ end
38
+ end
39
+
40
+ ::Zip::File.open(UNSPLITTED_FILENAME) do |zf|
41
+ zf.extract(ENTRY_TO_EXTRACT, EXTRACTED_FILENAME)
42
+
43
+ assert(File.exist?(EXTRACTED_FILENAME))
44
+ AssertEntry.assert_contents(EXTRACTED_FILENAME,
45
+ zf.get_input_stream(ENTRY_TO_EXTRACT) { |is| is.read })
46
+
47
+ File.unlink(EXTRACTED_FILENAME)
48
+
49
+ entry = zf.get_entry(ENTRY_TO_EXTRACT)
50
+ entry.extract(EXTRACTED_FILENAME)
51
+
52
+ assert(File.exist?(EXTRACTED_FILENAME))
53
+ AssertEntry.assert_contents(EXTRACTED_FILENAME,
54
+ entry.get_input_stream { |is| is.read })
55
+ end
56
+ end
57
+ end