rubyzip 1.0.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rubyzip might be problematic. Click here for more details.

Files changed (107) hide show
  1. checksums.yaml +6 -14
  2. data/README.md +173 -42
  3. data/Rakefile +10 -5
  4. data/TODO +0 -1
  5. data/lib/zip/central_directory.rb +55 -24
  6. data/lib/zip/compressor.rb +0 -0
  7. data/lib/zip/constants.rb +4 -2
  8. data/lib/zip/crypto/encryption.rb +11 -0
  9. data/lib/zip/crypto/null_encryption.rb +45 -0
  10. data/lib/zip/crypto/traditional_encryption.rb +99 -0
  11. data/lib/zip/decompressor.rb +2 -2
  12. data/lib/zip/deflater.rb +11 -6
  13. data/lib/zip/dos_time.rb +4 -5
  14. data/lib/zip/entry.rb +159 -97
  15. data/lib/zip/entry_set.rb +18 -18
  16. data/lib/zip/errors.rb +15 -6
  17. data/lib/zip/extra_field/generic.rb +8 -8
  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 +14 -14
  21. data/lib/zip/extra_field/unix.rb +8 -9
  22. data/lib/zip/extra_field/zip64.rb +44 -6
  23. data/lib/zip/extra_field/zip64_placeholder.rb +16 -0
  24. data/lib/zip/extra_field.rb +20 -8
  25. data/lib/zip/file.rb +126 -114
  26. data/lib/zip/filesystem.rb +140 -139
  27. data/lib/zip/inflater.rb +10 -9
  28. data/lib/zip/input_stream.rb +105 -80
  29. data/lib/zip/ioextras/abstract_input_stream.rb +15 -12
  30. data/lib/zip/ioextras/abstract_output_stream.rb +0 -2
  31. data/lib/zip/ioextras.rb +1 -3
  32. data/lib/zip/null_compressor.rb +2 -2
  33. data/lib/zip/null_decompressor.rb +4 -4
  34. data/lib/zip/null_input_stream.rb +2 -1
  35. data/lib/zip/output_stream.rb +57 -43
  36. data/lib/zip/pass_thru_compressor.rb +4 -4
  37. data/lib/zip/pass_thru_decompressor.rb +4 -5
  38. data/lib/zip/streamable_directory.rb +2 -2
  39. data/lib/zip/streamable_stream.rb +22 -13
  40. data/lib/zip/version.rb +1 -1
  41. data/lib/zip.rb +11 -2
  42. data/samples/example.rb +30 -40
  43. data/samples/example_filesystem.rb +16 -18
  44. data/samples/example_recursive.rb +35 -27
  45. data/samples/{gtkRubyzip.rb → gtk_ruby_zip.rb} +25 -27
  46. data/samples/qtzip.rb +19 -28
  47. data/samples/write_simple.rb +12 -13
  48. data/samples/zipfind.rb +29 -37
  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 +53 -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/mimetype +1 -0
  64. data/test/data/notzippedruby.rb +7 -0
  65. data/test/data/ntfs.zip +0 -0
  66. data/test/data/oddExtraField.zip +0 -0
  67. data/test/data/rubycode.zip +0 -0
  68. data/test/data/rubycode2.zip +0 -0
  69. data/test/data/test.xls +0 -0
  70. data/test/data/testDirectory.bin +0 -0
  71. data/test/data/zip64-sample.zip +0 -0
  72. data/test/data/zipWithDirs.zip +0 -0
  73. data/test/data/zipWithEncryption.zip +0 -0
  74. data/test/deflater_test.rb +65 -0
  75. data/test/encryption_test.rb +42 -0
  76. data/test/entry_set_test.rb +152 -0
  77. data/test/entry_test.rb +163 -0
  78. data/test/errors_test.rb +34 -0
  79. data/test/extra_field_test.rb +76 -0
  80. data/test/file_extract_directory_test.rb +54 -0
  81. data/test/file_extract_test.rb +83 -0
  82. data/test/file_permissions_test.rb +69 -0
  83. data/test/file_split_test.rb +57 -0
  84. data/test/file_test.rb +563 -0
  85. data/test/filesystem/dir_iterator_test.rb +58 -0
  86. data/test/filesystem/directory_test.rb +121 -0
  87. data/test/filesystem/file_mutating_test.rb +88 -0
  88. data/test/filesystem/file_nonmutating_test.rb +508 -0
  89. data/test/filesystem/file_stat_test.rb +64 -0
  90. data/test/gentestfiles.rb +122 -0
  91. data/test/inflater_test.rb +14 -0
  92. data/test/input_stream_test.rb +182 -0
  93. data/test/ioextras/abstract_input_stream_test.rb +102 -0
  94. data/test/ioextras/abstract_output_stream_test.rb +106 -0
  95. data/test/ioextras/fake_io_test.rb +18 -0
  96. data/test/local_entry_test.rb +154 -0
  97. data/test/output_stream_test.rb +128 -0
  98. data/test/pass_thru_compressor_test.rb +30 -0
  99. data/test/pass_thru_decompressor_test.rb +14 -0
  100. data/test/samples/example_recursive_test.rb +37 -0
  101. data/test/settings_test.rb +95 -0
  102. data/test/test_helper.rb +221 -0
  103. data/test/unicode_file_names_and_comments_test.rb +50 -0
  104. data/test/zip64_full_test.rb +51 -0
  105. data/test/zip64_support_test.rb +14 -0
  106. metadata +198 -22
  107. data/NEWS +0 -182
data/lib/zip/file.rb CHANGED
@@ -43,13 +43,13 @@ module Zip
43
43
  # interface for accessing the filesystem, ie. the File and Dir classes.
44
44
 
45
45
  class File < CentralDirectory
46
-
47
- CREATE = 1
48
- SPLIT_SIGNATURE = 0x08074b50
46
+ CREATE = 1
47
+ SPLIT_SIGNATURE = 0x08074b50
49
48
  ZIP64_EOCD_SIGNATURE = 0x06064b50
50
- MAX_SEGMENT_SIZE = 3221225472
51
- MIN_SEGMENT_SIZE = 65536
52
- DATA_BUFFER_SIZE = 8192
49
+ MAX_SEGMENT_SIZE = 3_221_225_472
50
+ MIN_SEGMENT_SIZE = 65_536
51
+ DATA_BUFFER_SIZE = 8192
52
+ IO_METHODS = [:tell, :seek, :read, :close]
53
53
 
54
54
  attr_reader :name
55
55
 
@@ -64,69 +64,78 @@ module Zip
64
64
 
65
65
  # Opens a zip archive. Pass true as the second parameter to create
66
66
  # a new archive if it doesn't exist already.
67
- def initialize(fileName, create = nil, buffer = false)
67
+ def initialize(file_name, create = nil, buffer = false, options = {})
68
68
  super()
69
- @name = fileName
69
+ @name = file_name
70
70
  @comment = ''
71
- @create = create
71
+ @create = create
72
72
  case
73
- when ::File.exists?(fileName) && !buffer
73
+ when !buffer && ::File.size?(file_name)
74
74
  @create = nil
75
+ @file_permissions = ::File.stat(file_name).mode
75
76
  ::File.open(name, 'rb') do |f|
76
77
  read_from_stream(f)
77
78
  end
78
79
  when create
80
+ @file_permissions = create_file_permissions
79
81
  @entry_set = EntrySet.new
82
+ when ::File.zero?(file_name)
83
+ raise Error, "File #{file_name} has zero size. Did you mean to pass the create flag?"
80
84
  else
81
- raise ZipError, "File #{fileName} not found"
85
+ raise Error, "File #{file_name} not found"
82
86
  end
83
- @storedEntries = @entry_set.dup
84
- @storedComment = @comment
85
- @restore_ownership = false
86
- @restore_permissions = false
87
- @restore_times = true
87
+ @stored_entries = @entry_set.dup
88
+ @stored_comment = @comment
89
+ @restore_ownership = options[:restore_ownership] || false
90
+ @restore_permissions = options[:restore_permissions] || true
91
+ @restore_times = options[:restore_times] || true
88
92
  end
89
93
 
90
94
  class << self
91
95
  # Same as #new. If a block is passed the ZipFile object is passed
92
96
  # to the block and is automatically closed afterwards just as with
93
97
  # ruby's builtin File.open method.
94
- def open(fileName, create = nil)
95
- zf = ::Zip::File.new(fileName, create)
96
- if block_given?
97
- begin
98
- yield zf
99
- ensure
100
- zf.close
101
- end
102
- else
103
- zf
98
+ def open(file_name, create = nil)
99
+ zf = ::Zip::File.new(file_name, create)
100
+ return zf unless block_given?
101
+ begin
102
+ yield zf
103
+ ensure
104
+ zf.close
104
105
  end
105
106
  end
106
107
 
107
108
  # Same as #open. But outputs data to a buffer instead of a file
108
109
  def add_buffer
109
- zf = ::Zip::File.new('', true, true)
110
+ io = ::StringIO.new('')
111
+ zf = ::Zip::File.new(io, true, true)
110
112
  yield zf
111
- zf.write_buffer
113
+ zf.write_buffer(io)
112
114
  end
113
115
 
114
116
  # Like #open, but reads zip archive contents from a String or open IO
115
117
  # stream, and outputs data to a buffer.
116
- # (This can be used to extract data from a
118
+ # (This can be used to extract data from a
117
119
  # downloaded zip archive without first saving it to disk.)
118
- def open_buffer(io)
119
- unless io.is_a?(IO) && io.is_a?(String)
120
- raise "Zip::ZipFile.open_buffer expects an argument of class String or IO. Found: #{io.class}"
120
+ def open_buffer(io, options = {})
121
+ unless IO_METHODS.map { |method| io.respond_to?(method) }.all? || io.is_a?(String)
122
+ raise "Zip::File.open_buffer expects a String or IO-like argument (responds to #{IO_METHODS.join(', ')}). Found: #{io.class}"
121
123
  end
122
- zf = ::Zip::File.new('', true, true)
123
- if io.is_a(::String)
124
+ if io.is_a?(::String)
124
125
  require 'stringio'
125
126
  io = ::StringIO.new(io)
127
+ elsif io.respond_to?(:binmode)
128
+ # https://github.com/rubyzip/rubyzip/issues/119
129
+ io.binmode
126
130
  end
131
+ zf = ::Zip::File.new(io, true, true, options)
127
132
  zf.read_from_stream(io)
128
133
  yield zf
129
- zf.write_buffer
134
+ begin
135
+ zf.write_buffer(io)
136
+ rescue IOError => e
137
+ raise unless e.message == 'not opened for writing'
138
+ end
130
139
  end
131
140
 
132
141
  # Iterates over the contents of the ZipFile. This is more efficient
@@ -173,9 +182,9 @@ module Zip
173
182
  # TODO: Make the code more understandable
174
183
  #
175
184
  def save_splited_part(zip_file, partial_zip_file_name, zip_file_size, szip_file_index, segment_size, segment_count)
176
- ssegment_size = zip_file_size - zip_file.pos
177
- ssegment_size = segment_size if ssegment_size > segment_size
178
- szip_file_name = "#{partial_zip_file_name}.#{'%03d'%(szip_file_index)}"
185
+ ssegment_size = zip_file_size - zip_file.pos
186
+ ssegment_size = segment_size if ssegment_size > segment_size
187
+ szip_file_name = "#{partial_zip_file_name}.#{format('%03d', szip_file_index)}"
179
188
  ::File.open(szip_file_name, 'wb') do |szip_file|
180
189
  if szip_file_index == 1
181
190
  ssegment_size = put_split_signature(szip_file, segment_size)
@@ -185,7 +194,7 @@ module Zip
185
194
  segment_bytes_left = ssegment_size - chunk_bytes
186
195
  buffer_size = segment_bytes_left < DATA_BUFFER_SIZE ? segment_bytes_left : DATA_BUFFER_SIZE
187
196
  chunk = zip_file.read(buffer_size)
188
- chunk_bytes += buffer_size
197
+ chunk_bytes += buffer_size
189
198
  szip_file << chunk
190
199
  # Info for track splitting
191
200
  yield segment_count, szip_file_index, chunk_bytes, ssegment_size if block_given?
@@ -195,14 +204,14 @@ module Zip
195
204
 
196
205
  # Splits an archive into parts with segment size
197
206
  def split(zip_file_name, segment_size = MAX_SEGMENT_SIZE, delete_zip_file = true, partial_zip_file_name = nil)
198
- raise ZipError, "File #{zip_file_name} not found" unless ::File.exists?(zip_file_name)
207
+ raise Error, "File #{zip_file_name} not found" unless ::File.exist?(zip_file_name)
199
208
  raise Errno::ENOENT, zip_file_name unless ::File.readable?(zip_file_name)
200
209
  zip_file_size = ::File.size(zip_file_name)
201
210
  segment_size = get_segment_size_for_split(segment_size)
202
211
  return if zip_file_size <= segment_size
203
212
  segment_count = get_segment_count_for_split(zip_file_size, segment_size)
204
213
  # Checking for correct zip structure
205
- self.open(zip_file_name) {}
214
+ open(zip_file_name) {}
206
215
  partial_zip_file_name = get_partial_zip_file_name(zip_file_name, partial_zip_file_name)
207
216
  szip_file_index = 0
208
217
  ::File.open(zip_file_name, 'rb') do |zip_file|
@@ -216,7 +225,6 @@ module Zip
216
225
  end
217
226
  end
218
227
 
219
-
220
228
  # Returns an input stream to the specified entry. If a block is passed
221
229
  # the stream object is passed to the block and the stream is automatically
222
230
  # closed afterwards just as with ruby's builtin File.open method.
@@ -224,19 +232,26 @@ module Zip
224
232
  get_entry(entry).get_input_stream(&aProc)
225
233
  end
226
234
 
227
- # Returns an output stream to the specified entry. If a block is passed
228
- # the stream object is passed to the block and the stream is automatically
229
- # closed afterwards just as with ruby's builtin File.open method.
230
- def get_output_stream(entry, permissionInt = nil, &aProc)
231
- newEntry = entry.kind_of?(Entry) ? entry : Entry.new(@name, entry.to_s)
232
- if newEntry.directory?
235
+ # Returns an output stream to the specified entry. If entry is not an instance
236
+ # of Zip::Entry, a new Zip::Entry will be initialized using the arguments
237
+ # specified. If a block is passed the stream object is passed to the block and
238
+ # the stream is automatically closed afterwards just as with ruby's builtin
239
+ # File.open method.
240
+ def get_output_stream(entry, permission_int = nil, comment = nil, extra = nil, compressed_size = nil, crc = nil, compression_method = nil, size = nil, time = nil, &aProc)
241
+ new_entry =
242
+ if entry.kind_of?(Entry)
243
+ entry
244
+ else
245
+ Entry.new(@name, entry.to_s, comment, extra, compressed_size, crc, compression_method, size, time)
246
+ end
247
+ if new_entry.directory?
233
248
  raise ArgumentError,
234
- "cannot open stream to directory entry - '#{newEntry}'"
249
+ "cannot open stream to directory entry - '#{new_entry}'"
235
250
  end
236
- newEntry.unix_perms = permissionInt
237
- zipStreamableEntry = StreamableStream.new(newEntry)
238
- @entry_set << zipStreamableEntry
239
- zipStreamableEntry.get_output_stream(&aProc)
251
+ new_entry.unix_perms = permission_int
252
+ zip_streamable_entry = StreamableStream.new(new_entry)
253
+ @entry_set << zip_streamable_entry
254
+ zip_streamable_entry.get_output_stream(&aProc)
240
255
  end
241
256
 
242
257
  # Returns the name of the zip archive
@@ -250,12 +265,13 @@ module Zip
250
265
  end
251
266
 
252
267
  # Convenience method for adding the contents of a file to the archive
253
- def add(entry, srcPath, &continue_on_exists_proc)
254
- continue_on_exists_proc ||= proc { Zip.continue_on_exists_proc }
255
- check_entry_exists(entry, continue_on_exists_proc, "add")
256
- newEntry = entry.kind_of?(Entry) ? entry : Entry.new(@name, entry.to_s)
257
- newEntry.gather_fileinfo_from_srcpath(srcPath)
258
- @entry_set << newEntry
268
+ def add(entry, src_path, &continue_on_exists_proc)
269
+ continue_on_exists_proc ||= proc { ::Zip.continue_on_exists_proc }
270
+ check_entry_exists(entry, continue_on_exists_proc, 'add')
271
+ new_entry = entry.kind_of?(::Zip::Entry) ? entry : ::Zip::Entry.new(@name, entry.to_s)
272
+ new_entry.gather_fileinfo_from_srcpath(src_path)
273
+ new_entry.dirty = true
274
+ @entry_set << new_entry
259
275
  end
260
276
 
261
277
  # Removes the specified entry.
@@ -282,7 +298,7 @@ module Zip
282
298
 
283
299
  # Extracts entry to file dest_path.
284
300
  def extract(entry, dest_path, &block)
285
- block ||= proc { ::Zip.on_exists_proc }
301
+ block ||= proc { ::Zip.on_exists_proc }
286
302
  found_entry = get_entry(entry)
287
303
  found_entry.extract(dest_path, &block)
288
304
  end
@@ -290,31 +306,27 @@ module Zip
290
306
  # Commits changes that has been made since the previous commit to
291
307
  # the zip archive.
292
308
  def commit
293
- return if !commit_required?
294
- on_success_replace(name) {
295
- |tmpFile|
296
- OutputStream.open(tmpFile) {
297
- |zos|
298
-
299
- @entry_set.each {
300
- |e|
309
+ return unless commit_required?
310
+ on_success_replace do |tmp_file|
311
+ ::Zip::OutputStream.open(tmp_file) do |zos|
312
+ @entry_set.each do |e|
301
313
  e.write_to_zip_output_stream(zos)
302
314
  e.dirty = false
303
- }
315
+ e.clean_up
316
+ end
304
317
  zos.comment = comment
305
- }
318
+ end
306
319
  true
307
- }
320
+ end
308
321
  initialize(name)
309
322
  end
310
323
 
311
324
  # Write buffer write changes to buffer and return
312
- def write_buffer
313
- buffer = OutputStream.write_buffer do |zos|
325
+ def write_buffer(io = ::StringIO.new(''))
326
+ ::Zip::OutputStream.write_buffer(io) do |zos|
314
327
  @entry_set.each { |e| e.write_to_zip_output_stream(zos) }
315
328
  zos.comment = comment
316
329
  end
317
- return buffer
318
330
  end
319
331
 
320
332
  # Closes the zip file committing any changes that has been made.
@@ -328,7 +340,7 @@ module Zip
328
340
  @entry_set.each do |e|
329
341
  return true if e.dirty
330
342
  end
331
- @comment != @storedComment || @entry_set != @storedEntries || @create == File::CREATE
343
+ @comment != @stored_comment || @entry_set != @stored_entries || @create == ::Zip::File::CREATE
332
344
  end
333
345
 
334
346
  # Searches for entry with the specified name. Returns nil if
@@ -345,73 +357,73 @@ module Zip
345
357
  # Searches for an entry just as find_entry, but throws Errno::ENOENT
346
358
  # if no entry is found.
347
359
  def get_entry(entry)
348
- selectedEntry = find_entry(entry)
349
- unless selectedEntry
350
- raise Errno::ENOENT, entry
351
- end
352
- selectedEntry.restore_ownership = @restore_ownership
353
- selectedEntry.restore_permissions = @restore_permissions
354
- selectedEntry.restore_times = @restore_times
355
- selectedEntry
360
+ selected_entry = find_entry(entry)
361
+ raise Errno::ENOENT, entry unless selected_entry
362
+ selected_entry.restore_ownership = @restore_ownership
363
+ selected_entry.restore_permissions = @restore_permissions
364
+ selected_entry.restore_times = @restore_times
365
+ selected_entry
356
366
  end
357
367
 
358
368
  # Creates a directory
359
369
  def mkdir(entryName, permissionInt = 0755)
360
- if find_entry(entryName)
361
- raise Errno::EEXIST, "File exists - #{entryName}"
362
- end
370
+ raise Errno::EEXIST, "File exists - #{entryName}" if find_entry(entryName)
363
371
  entryName = entryName.dup.to_s
364
372
  entryName << '/' unless entryName.end_with?('/')
365
- @entry_set << StreamableDirectory.new(@name, entryName, nil, permissionInt)
373
+ @entry_set << ::Zip::StreamableDirectory.new(@name, entryName, nil, permissionInt)
366
374
  end
367
375
 
368
376
  private
369
377
 
370
- def is_directory(newEntry, srcPath)
378
+ def directory?(newEntry, srcPath)
371
379
  srcPathIsDirectory = ::File.directory?(srcPath)
372
- if newEntry.is_directory && !srcPathIsDirectory
380
+ if newEntry.directory? && !srcPathIsDirectory
373
381
  raise ArgumentError,
374
- "entry name '#{newEntry}' indicates directory entry, but "+
375
- "'#{srcPath}' is not a directory"
376
- elsif !newEntry.is_directory && srcPathIsDirectory
377
- newEntry.name += "/"
382
+ "entry name '#{newEntry}' indicates directory entry, but " \
383
+ "'#{srcPath}' is not a directory"
384
+ elsif !newEntry.directory? && srcPathIsDirectory
385
+ newEntry.name += '/'
378
386
  end
379
- newEntry.is_directory && srcPathIsDirectory
387
+ newEntry.directory? && srcPathIsDirectory
380
388
  end
381
389
 
382
390
  def check_entry_exists(entryName, continue_on_exists_proc, procedureName)
383
391
  continue_on_exists_proc ||= proc { Zip.continue_on_exists_proc }
384
- if @entry_set.include?(entryName)
385
- if continue_on_exists_proc.call
386
- remove get_entry(entryName)
387
- else
388
- raise ZipEntryExistsError,
389
- procedureName + " failed. Entry #{entryName} already exists"
390
- end
392
+ return unless @entry_set.include?(entryName)
393
+ if continue_on_exists_proc.call
394
+ remove get_entry(entryName)
395
+ else
396
+ raise ::Zip::EntryExistsError,
397
+ procedureName + " failed. Entry #{entryName} already exists"
391
398
  end
392
399
  end
393
400
 
394
401
  def check_file(path)
395
- unless ::File.readable?(path)
396
- raise Errno::ENOENT, path
397
- end
402
+ raise Errno::ENOENT, path unless ::File.readable?(path)
398
403
  end
399
404
 
400
- def on_success_replace(aFilename)
401
- tmpfile = get_tempfile
402
- tmpFilename = tmpfile.path
403
- tmpfile.close
404
- if yield tmpFilename
405
- ::File.rename(tmpFilename, name)
405
+ def on_success_replace
406
+ tmp_filename = create_tmpname
407
+ if yield tmp_filename
408
+ ::File.rename(tmp_filename, name)
409
+ ::File.chmod(@file_permissions, name) if defined?(@file_permissions)
406
410
  end
411
+ ensure
412
+ ::File.unlink(tmp_filename) if ::File.exist?(tmp_filename)
407
413
  end
408
414
 
409
- def get_tempfile
410
- tempFile = Tempfile.new(::File.basename(name), ::File.dirname(name))
411
- tempFile.binmode
412
- tempFile
415
+ def create_tmpname
416
+ dirname, basename = ::File.split(name)
417
+ ::Dir::Tmpname.create(basename, dirname) do |tmpname|
418
+ opts = {perm: 0600, mode: ::File::CREAT | ::File::WRONLY | ::File::EXCL}
419
+ f = File.open(tmpname, opts)
420
+ f.close
421
+ end
413
422
  end
414
423
 
424
+ def create_file_permissions
425
+ ::Zip::RUNNING_ON_WINDOWS ? 0644 : 0666 - ::File.umask
426
+ end
415
427
  end
416
428
  end
417
429