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/deflater.rb CHANGED
@@ -1,23 +1,28 @@
1
1
  module Zip
2
2
  class Deflater < Compressor #:nodoc:all
3
-
4
- def initialize(output_stream, level = ::Zlib::DEFAULT_COMPRESSION)
3
+ def initialize(output_stream, level = Zip.default_compression, encrypter = NullEncrypter.new)
5
4
  super()
6
5
  @output_stream = output_stream
7
6
  @zlib_deflater = ::Zlib::Deflate.new(level, -::Zlib::MAX_WBITS)
8
7
  @size = 0
9
8
  @crc = ::Zlib.crc32
9
+ @encrypter = encrypter
10
10
  end
11
11
 
12
- def << (data)
12
+ def <<(data)
13
13
  val = data.to_s
14
- @crc = Zlib::crc32(val, @crc)
14
+ @crc = Zlib.crc32(val, @crc)
15
15
  @size += val.bytesize
16
- @output_stream << @zlib_deflater.deflate(data)
16
+ buffer = @zlib_deflater.deflate(data)
17
+ if buffer.empty?
18
+ @output_stream
19
+ else
20
+ @output_stream << @encrypter.encrypt(buffer)
21
+ end
17
22
  end
18
23
 
19
24
  def finish
20
- @output_stream << @zlib_deflater.finish until @zlib_deflater.finished?
25
+ @output_stream << @encrypter.encrypt(@zlib_deflater.finish) until @zlib_deflater.finished?
21
26
  end
22
27
 
23
28
  attr_reader :size, :crc
data/lib/zip/dos_time.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  module Zip
2
2
  class DOSTime < Time #:nodoc:all
3
-
4
- #MS-DOS File Date and Time format as used in Interrupt 21H Function 57H:
3
+ # MS-DOS File Date and Time format as used in Interrupt 21H Function 57H:
5
4
 
6
5
  # Register CX, the Time:
7
6
  # Bits 0-4 2 second increments (0-29)
@@ -14,7 +13,7 @@ module Zip
14
13
  # bits 9-15 year (four digit year minus 1980)
15
14
 
16
15
  def to_binary_dos_time
17
- (sec/2) +
16
+ (sec / 2) +
18
17
  (min << 5) +
19
18
  (hour << 11)
20
19
  end
@@ -27,7 +26,7 @@ module Zip
27
26
 
28
27
  # Dos time is only stored with two seconds accuracy
29
28
  def dos_equals(other)
30
- to_i/2 == other.to_i/2
29
+ to_i / 2 == other.to_i / 2
31
30
  end
32
31
 
33
32
  def self.parse_binary_dos_format(binaryDosDate, binaryDosTime)
@@ -38,7 +37,7 @@ module Zip
38
37
  month = (0b111100000 & binaryDosDate) >> 5
39
38
  year = ((0b1111111000000000 & binaryDosDate) >> 9) + 1980
40
39
  begin
41
- self.local(year, month, day, hour, minute, second)
40
+ local(year, month, day, hour, minute, second)
42
41
  end
43
42
  end
44
43
  end
data/lib/zip/entry.rb CHANGED
@@ -15,13 +15,13 @@ module Zip
15
15
 
16
16
  def set_default_vars_values
17
17
  @local_header_offset = 0
18
- @local_header_size = 0
18
+ @local_header_size = nil # not known until local entry is created or read
19
19
  @internal_file_attributes = 1
20
20
  @external_file_attributes = 0
21
21
  @header_signature = ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
22
22
 
23
23
  @version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT
24
- @version = 52 # this library's version
24
+ @version = VERSION_MADE_BY
25
25
 
26
26
  @ftype = nil # unspecified or unknown
27
27
  @filepath = nil
@@ -39,15 +39,14 @@ module Zip
39
39
  @unix_uid = nil
40
40
  @unix_gid = nil
41
41
  @unix_perms = nil
42
- #@posix_acl = nil
43
- #@ntfs_acl = nil
42
+ # @posix_acl = nil
43
+ # @ntfs_acl = nil
44
44
  @dirty = false
45
45
  end
46
46
 
47
47
  def check_name(name)
48
- if name.start_with?('/')
49
- raise ::Zip::ZipEntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /"
50
- end
48
+ return unless name.start_with?('/')
49
+ raise ::Zip::EntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /"
51
50
  end
52
51
 
53
52
  def initialize(*args)
@@ -68,12 +67,14 @@ module Zip
68
67
  @time = args[8] || ::Zip::DOSTime.now
69
68
 
70
69
  @ftype = name_is_directory? ? :directory : :file
71
- @extra = ::Zip::ExtraField.new(@extra.to_s) unless ::Zip::ExtraField === @extra
70
+ @extra = ::Zip::ExtraField.new(@extra.to_s) unless @extra.is_a?(::Zip::ExtraField)
72
71
  end
73
72
 
74
73
  def time
75
74
  if @extra['UniversalTime']
76
75
  @extra['UniversalTime'].mtime
76
+ elsif @extra['NTFS']
77
+ @extra['NTFS'].mtime
77
78
  else
78
79
  # Standard time field in central directory has local time
79
80
  # under archive creator. Then, we can't get timezone.
@@ -81,18 +82,18 @@ module Zip
81
82
  end
82
83
  end
83
84
 
84
- alias :mtime :time
85
+ alias mtime time
85
86
 
86
87
  def time=(value)
87
- unless @extra.member?('UniversalTime')
88
+ unless @extra.member?('UniversalTime') || @extra.member?('NTFS')
88
89
  @extra.create('UniversalTime')
89
90
  end
90
- @extra['UniversalTime'].mtime = value
91
+ (@extra['UniversalTime'] || @extra['NTFS']).mtime = value
91
92
  @time = value
92
93
  end
93
94
 
94
95
  def file_type_is?(type)
95
- raise ZipInternalError, "current filetype is unknown: #{self.inspect}" unless @ftype
96
+ raise InternalError, "current filetype is unknown: #{inspect}" unless @ftype
96
97
  @ftype == type
97
98
  end
98
99
 
@@ -124,17 +125,24 @@ module Zip
124
125
  end
125
126
 
126
127
  def calculate_local_header_size #:nodoc:all
127
- fix_zip64_sizes!
128
128
  LOCAL_ENTRY_STATIC_HEADER_LENGTH + name_size + extra_size
129
129
  end
130
130
 
131
+ # check before rewriting an entry (after file sizes are known)
132
+ # that we didn't change the header size (and thus clobber file data or something)
133
+ def verify_local_header_size!
134
+ return if @local_header_size.nil?
135
+ new_size = calculate_local_header_size
136
+ raise Error, "local header size changed (#{@local_header_size} -> #{new_size})" if @local_header_size != new_size
137
+ end
138
+
131
139
  def cdir_header_size #:nodoc:all
132
140
  CDIR_ENTRY_STATIC_HEADER_LENGTH + name_size +
133
141
  (@extra ? @extra.c_dir_size : 0) + comment_size
134
142
  end
135
143
 
136
144
  def next_header_offset #:nodoc:all
137
- local_entry_offset + self.compressed_size
145
+ local_entry_offset + compressed_size + data_descriptor_size
138
146
  end
139
147
 
140
148
  # Extracts entry to file dest_path (defaults to @name).
@@ -142,9 +150,9 @@ module Zip
142
150
  block ||= proc { ::Zip.on_exists_proc }
143
151
 
144
152
  if directory? || file? || symlink?
145
- self.__send__("create_#{@ftype}", dest_path, &block)
153
+ __send__("create_#{@ftype}", dest_path, &block)
146
154
  else
147
- raise RuntimeError, "unknown file type #{self.inspect}"
155
+ raise "unknown file type #{inspect}"
148
156
  end
149
157
 
150
158
  self
@@ -154,8 +162,6 @@ module Zip
154
162
  @name
155
163
  end
156
164
 
157
- protected
158
-
159
165
  class << self
160
166
  def read_zip_short(io) # :nodoc:
161
167
  io.read(2).unpack('v')[0]
@@ -166,25 +172,29 @@ module Zip
166
172
  end
167
173
 
168
174
  def read_zip_64_long(io) # :nodoc:
169
- io.read(8).unpack('V')[0]
175
+ io.read(8).unpack('Q<')[0]
170
176
  end
171
177
 
172
178
  def read_c_dir_entry(io) #:nodoc:all
173
- entry = new(io.path)
179
+ path = if io.respond_to?(:path)
180
+ io.path
181
+ else
182
+ io
183
+ end
184
+ entry = new(path)
174
185
  entry.read_c_dir_entry(io)
175
186
  entry
176
- rescue ZipError
187
+ rescue Error
177
188
  nil
178
189
  end
179
190
 
180
191
  def read_local_entry(io)
181
- entry = new(io.path)
192
+ entry = new(io)
182
193
  entry.read_local_entry(io)
183
194
  entry
184
- rescue ZipError
195
+ rescue Error
185
196
  nil
186
197
  end
187
-
188
198
  end
189
199
 
190
200
  public
@@ -207,16 +217,16 @@ module Zip
207
217
  def read_local_entry(io) #:nodoc:all
208
218
  @local_header_offset = io.tell
209
219
 
210
- static_sized_fields_buf = io.read(::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH)
220
+ static_sized_fields_buf = io.read(::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH) || ''
211
221
 
212
222
  unless static_sized_fields_buf.bytesize == ::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH
213
- raise ZipError, "Premature end of file. Not enough data for zip entry local header"
223
+ raise Error, 'Premature end of file. Not enough data for zip entry local header'
214
224
  end
215
225
 
216
226
  unpack_local_entry(static_sized_fields_buf)
217
227
 
218
228
  unless @header_signature == ::Zip::LOCAL_ENTRY_SIGNATURE
219
- raise ::Zip::ZipError, "Zip local header magic not found at location '#{local_header_offset}'"
229
+ raise ::Zip::Error, "Zip local header magic not found at location '#{local_header_offset}'"
220
230
  end
221
231
  set_time(@last_mod_date, @last_mod_time)
222
232
 
@@ -226,18 +236,20 @@ module Zip
226
236
  @name.gsub!('\\', '/')
227
237
 
228
238
  if extra && extra.bytesize != @extra_length
229
- raise ::Zip::ZipError, "Truncated local zip entry header"
239
+ raise ::Zip::Error, 'Truncated local zip entry header'
230
240
  else
231
- if ::Zip::ExtraField === @extra
232
- @extra.merge(extra)
241
+ if @extra.is_a?(::Zip::ExtraField)
242
+ @extra.merge(extra) if extra
233
243
  else
234
244
  @extra = ::Zip::ExtraField.new(extra)
235
245
  end
236
246
  end
247
+ parse_zip64_extra(true)
237
248
  @local_header_size = calculate_local_header_size
238
249
  end
239
250
 
240
251
  def pack_local_entry
252
+ zip64 = @extra['Zip64']
241
253
  [::Zip::LOCAL_ENTRY_SIGNATURE,
242
254
  @version_needed_to_extract, # version needed to extract
243
255
  @gp_flags, # @gp_flags ,
@@ -245,19 +257,22 @@ module Zip
245
257
  @time.to_binary_dos_time, # @last_mod_time ,
246
258
  @time.to_binary_dos_date, # @last_mod_date ,
247
259
  @crc,
248
- @compressed_size,
249
- @size,
260
+ (zip64 && zip64.compressed_size) ? 0xFFFFFFFF : @compressed_size,
261
+ (zip64 && zip64.original_size) ? 0xFFFFFFFF : @size,
250
262
  name_size,
251
263
  @extra ? @extra.local_size : 0].pack('VvvvvvVVVvv')
252
264
  end
253
265
 
254
- def write_local_entry(io) #:nodoc:all
266
+ def write_local_entry(io, rewrite = false) #:nodoc:all
267
+ prep_zip64_extra(true)
268
+ verify_local_header_size! if rewrite
255
269
  @local_header_offset = io.tell
256
270
 
257
271
  io << pack_local_entry
258
272
 
259
273
  io << @name
260
- io << (@extra ? @extra.to_local_bin : '')
274
+ io << @extra.to_local_bin if @extra
275
+ @local_header_size = io.tell - @local_header_offset
261
276
  end
262
277
 
263
278
  def unpack_c_dir_entry(buf)
@@ -296,8 +311,8 @@ module Zip
296
311
  when ::Zip::FILE_TYPE_SYMLINK
297
312
  :symlink
298
313
  else
299
- #best case guess for whether it is a file or not
300
- #Otherwise this would be set to unknown and that entry would never be able to extracted
314
+ # best case guess for whether it is a file or not
315
+ # Otherwise this would be set to unknown and that entry would never be able to extracted
301
316
  if name_is_directory?
302
317
  :directory
303
318
  else
@@ -314,21 +329,18 @@ module Zip
314
329
  end
315
330
 
316
331
  def check_c_dir_entry_static_header_length(buf)
317
- unless buf.bytesize == ::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH
318
- raise ZipError, 'Premature end of file. Not enough data for zip cdir entry header'
319
- end
332
+ return if buf.bytesize == ::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH
333
+ raise Error, 'Premature end of file. Not enough data for zip cdir entry header'
320
334
  end
321
335
 
322
336
  def check_c_dir_entry_signature
323
- unless header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
324
- raise ZipError, "Zip local header magic not found at location '#{local_header_offset}'"
325
- end
337
+ return if header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
338
+ raise Error, "Zip local header magic not found at location '#{local_header_offset}'"
326
339
  end
327
340
 
328
341
  def check_c_dir_entry_comment_size
329
- unless @comment && @comment.bytesize == @comment_length
330
- raise ::Zip::ZipError, "Truncated cdir zip entry header"
331
- end
342
+ return if @comment && @comment.bytesize == @comment_length
343
+ raise ::Zip::Error, 'Truncated cdir zip entry header'
332
344
  end
333
345
 
334
346
  def read_c_dir_extra_field(io)
@@ -345,29 +357,28 @@ module Zip
345
357
  unpack_c_dir_entry(static_sized_fields_buf)
346
358
  check_c_dir_entry_signature
347
359
  set_time(@last_mod_date, @last_mod_time)
348
- @name = io.read(@name_length).gsub('\\', '/')
360
+ @name = io.read(@name_length).tr('\\', '/')
349
361
  read_c_dir_extra_field(io)
350
362
  @comment = io.read(@comment_length)
351
363
  check_c_dir_entry_comment_size
352
364
  set_ftype_from_c_dir_entry
353
- @local_header_size = calculate_local_header_size
365
+ parse_zip64_extra(false)
354
366
  end
355
367
 
356
368
  def file_stat(path) # :nodoc:
357
369
  if @follow_symlinks
358
- ::File::stat(path)
370
+ ::File.stat(path)
359
371
  else
360
- ::File::lstat(path)
372
+ ::File.lstat(path)
361
373
  end
362
374
  end
363
375
 
364
376
  def get_extra_attributes_from_path(path) # :nodoc:
365
- unless Zip::RUNNING_ON_WINDOWS
366
- stat = file_stat(path)
367
- @unix_uid = stat.uid
368
- @unix_gid = stat.gid
369
- @unix_perms = stat.mode & 07777
370
- end
377
+ return if Zip::RUNNING_ON_WINDOWS
378
+ stat = file_stat(path)
379
+ @unix_uid = stat.uid
380
+ @unix_gid = stat.gid
381
+ @unix_perms = stat.mode & 07777
371
382
  end
372
383
 
373
384
  def set_unix_permissions_on_path(dest_path)
@@ -381,7 +392,7 @@ module Zip
381
392
  end
382
393
 
383
394
  def set_extra_attributes_on_path(dest_path) # :nodoc:
384
- return unless (file? || directory?)
395
+ return unless file? || directory?
385
396
 
386
397
  case @fstype
387
398
  when ::Zip::FSTYPE_UNIX
@@ -390,6 +401,7 @@ module Zip
390
401
  end
391
402
 
392
403
  def pack_c_dir_entry
404
+ zip64 = @extra['Zip64']
393
405
  [
394
406
  @header_signature,
395
407
  @version, # version of encoding software
@@ -400,15 +412,15 @@ module Zip
400
412
  @time.to_binary_dos_time, # @last_mod_time ,
401
413
  @time.to_binary_dos_date, # @last_mod_date ,
402
414
  @crc,
403
- @compressed_size,
404
- @size,
415
+ (zip64 && zip64.compressed_size) ? 0xFFFFFFFF : @compressed_size,
416
+ (zip64 && zip64.original_size) ? 0xFFFFFFFF : @size,
405
417
  name_size,
406
418
  @extra ? @extra.c_dir_size : 0,
407
419
  comment_size,
408
- 0, # disk number start
420
+ (zip64 && zip64.disk_start_number) ? 0xFFFF : 0, # disk number start
409
421
  @internal_file_attributes, # file type (binary=0, text=1)
410
422
  @external_file_attributes, # native filesystem attributes
411
- @local_header_offset,
423
+ (zip64 && zip64.relative_header_offset) ? 0xFFFFFFFF : @local_header_offset,
412
424
  @name,
413
425
  @extra,
414
426
  @comment
@@ -416,6 +428,7 @@ module Zip
416
428
  end
417
429
 
418
430
  def write_c_dir_entry(io) #:nodoc:all
431
+ prep_zip64_extra(false)
419
432
  case @fstype
420
433
  when ::Zip::FSTYPE_UNIX
421
434
  ft = case @ftype
@@ -446,21 +459,21 @@ module Zip
446
459
  return false unless other.class == self.class
447
460
  # Compares contents of local entry and exposed fields
448
461
  keys_equal = %w(compression_method crc compressed_size size name extra filepath).all? do |k|
449
- other.__send__(k.to_sym) == self.__send__(k.to_sym)
462
+ other.__send__(k.to_sym) == __send__(k.to_sym)
450
463
  end
451
- keys_equal && self.time.dos_equals(other.time)
464
+ keys_equal && time.dos_equals(other.time)
452
465
  end
453
466
 
454
- def <=> (other)
455
- self.to_s <=> other.to_s
467
+ def <=>(other)
468
+ to_s <=> other.to_s
456
469
  end
457
470
 
458
471
  # Returns an IO like object for the given ZipEntry.
459
472
  # Warning: may behave weird with symlinks.
460
473
  def get_input_stream(&block)
461
474
  if @ftype == :directory
462
- yield(::Zip::NullInputStream.instance) if block_given?
463
- ::Zip::NullInputStream.instance
475
+ yield ::Zip::NullInputStream if block_given?
476
+ ::Zip::NullInputStream
464
477
  elsif @filepath
465
478
  case @ftype
466
479
  when :file
@@ -475,6 +488,7 @@ module Zip
475
488
  end
476
489
  else
477
490
  zis = ::Zip::InputStream.new(@zipfile, local_header_offset)
491
+ zis.instance_variable_set(:@internal, true)
478
492
  zis.get_next_entry
479
493
  if block_given?
480
494
  begin
@@ -494,22 +508,22 @@ module Zip
494
508
  when 'file'
495
509
  if name_is_directory?
496
510
  raise ArgumentError,
497
- "entry name '#{newEntry}' indicates directory entry, but "+
498
- "'#{src_path}' is not a directory"
511
+ "entry name '#{newEntry}' indicates directory entry, but " \
512
+ "'#{src_path}' is not a directory"
499
513
  end
500
514
  :file
501
515
  when 'directory'
502
- @name += "/" unless name_is_directory?
516
+ @name += '/' unless name_is_directory?
503
517
  :directory
504
518
  when 'link'
505
519
  if name_is_directory?
506
520
  raise ArgumentError,
507
- "entry name '#{newEntry}' indicates directory entry, but "+
508
- "'#{src_path}' is not a directory"
521
+ "entry name '#{newEntry}' indicates directory entry, but " \
522
+ "'#{src_path}' is not a directory"
509
523
  end
510
524
  :symlink
511
525
  else
512
- raise RuntimeError, "unknown file type: #{src_path.inspect} #{stat.inspect}"
526
+ raise "unknown file type: #{src_path.inspect} #{stat.inspect}"
513
527
  end
514
528
 
515
529
  @filepath = src_path
@@ -518,9 +532,9 @@ module Zip
518
532
 
519
533
  def write_to_zip_output_stream(zip_output_stream) #:nodoc:all
520
534
  if @ftype == :directory
521
- zip_output_stream.put_next_entry(self)
535
+ zip_output_stream.put_next_entry(self, nil, nil, ::Zip::Entry::STORED)
522
536
  elsif @filepath
523
- zip_output_stream.put_next_entry(self, nil, nil, nil)
537
+ zip_output_stream.put_next_entry(self, nil, nil, compression_method || ::Zip::Entry::DEFLATED)
524
538
  get_input_stream { |is| ::Zip::IOExtras.copy_stream(zip_output_stream, is) }
525
539
  else
526
540
  zip_output_stream.copy_raw_entry(self)
@@ -530,11 +544,19 @@ module Zip
530
544
  def parent_as_string
531
545
  entry_name = name.chomp('/')
532
546
  slash_index = entry_name.rindex('/')
533
- slash_index ? entry_name.slice(0, slash_index+1) : nil
547
+ slash_index ? entry_name.slice(0, slash_index + 1) : nil
534
548
  end
535
549
 
536
550
  def get_raw_input_stream(&block)
537
- ::File.open(@zipfile, "rb", &block)
551
+ if @zipfile.respond_to?(:seek) && @zipfile.respond_to?(:read)
552
+ yield @zipfile
553
+ else
554
+ ::File.open(@zipfile, 'rb', &block)
555
+ end
556
+ end
557
+
558
+ def clean_up
559
+ # By default, do nothing
538
560
  end
539
561
 
540
562
  private
@@ -542,20 +564,20 @@ module Zip
542
564
  def set_time(binary_dos_date, binary_dos_time)
543
565
  @time = ::Zip::DOSTime.parse_binary_dos_format(binary_dos_date, binary_dos_time)
544
566
  rescue ArgumentError
545
- puts "Invalid date/time in zip entry"
567
+ warn 'Invalid date/time in zip entry' if ::Zip.warn_invalid_date
546
568
  end
547
569
 
548
- def create_file(dest_path, continue_on_exists_proc = proc { Zip.continue_on_exists_proc })
549
- if ::File.exists?(dest_path) && !yield(self, dest_path)
550
- raise ::Zip::ZipDestinationFileExistsError,
570
+ def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exists_proc })
571
+ if ::File.exist?(dest_path) && !yield(self, dest_path)
572
+ raise ::Zip::DestinationFileExistsError,
551
573
  "Destination '#{dest_path}' already exists"
552
574
  end
553
- ::File.open(dest_path, "wb") do |os|
575
+ ::File.open(dest_path, 'wb') do |os|
554
576
  get_input_stream do |is|
555
577
  set_extra_attributes_on_path(dest_path)
556
578
 
557
579
  buf = ''
558
- while buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf)
580
+ while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf))
559
581
  os << buf
560
582
  end
561
583
  end
@@ -564,13 +586,13 @@ module Zip
564
586
 
565
587
  def create_directory(dest_path)
566
588
  return if ::File.directory?(dest_path)
567
- if ::File.exists?(dest_path)
589
+ if ::File.exist?(dest_path)
568
590
  if block_given? && yield(self, dest_path)
569
- ::FileUtils::rm_f dest_path
591
+ ::FileUtils.rm_f dest_path
570
592
  else
571
- raise ::Zip::ZipDestinationFileExistsError,
572
- "Cannot create directory '#{dest_path}'. "+
573
- "A file already exists with that name"
593
+ raise ::Zip::DestinationFileExistsError,
594
+ "Cannot create directory '#{dest_path}'. " \
595
+ 'A file already exists with that name'
574
596
  end
575
597
  end
576
598
  ::FileUtils.mkdir_p(dest_path)
@@ -593,27 +615,67 @@ module Zip
593
615
  if ::File.readlink(dest_path) == linkto
594
616
  return
595
617
  else
596
- raise ZipDestinationFileExistsError,
597
- "Cannot create symlink '#{dest_path}'. "+
598
- "A symlink already exists with that name"
618
+ raise ::Zip::DestinationFileExistsError,
619
+ "Cannot create symlink '#{dest_path}'. " \
620
+ 'A symlink already exists with that name'
599
621
  end
600
622
  else
601
- raise ZipDestinationFileExistsError,
602
- "Cannot create symlink '#{dest_path}'. "+
603
- "A file already exists with that name"
623
+ raise ::Zip::DestinationFileExistsError,
624
+ "Cannot create symlink '#{dest_path}'. " \
625
+ 'A file already exists with that name'
604
626
  end
605
627
  end
606
628
 
607
629
  ::File.symlink(linkto, dest_path)
608
630
  end
609
631
 
610
- def fix_zip64_sizes! #:nodoc:all
611
- if zip64 = @extra["Zip64"]
612
- @size = zip64.original_size
613
- @compressed_size = zip64.compressed_size
632
+ # apply missing data from the zip64 extra information field, if present
633
+ # (required when file sizes exceed 2**32, but can be used for all files)
634
+ def parse_zip64_extra(for_local_header) #:nodoc:all
635
+ return if @extra['Zip64'].nil?
636
+ if for_local_header
637
+ @size, @compressed_size = @extra['Zip64'].parse(@size, @compressed_size)
638
+ else
639
+ @size, @compressed_size, @local_header_offset = @extra['Zip64'].parse(@size, @compressed_size, @local_header_offset)
614
640
  end
615
641
  end
616
642
 
643
+ def data_descriptor_size
644
+ (@gp_flags & 0x0008) > 0 ? 16 : 0
645
+ end
646
+
647
+ # create a zip64 extra information field if we need one
648
+ def prep_zip64_extra(for_local_header) #:nodoc:all
649
+ return unless ::Zip.write_zip64_support
650
+ need_zip64 = @size >= 0xFFFFFFFF || @compressed_size >= 0xFFFFFFFF
651
+ need_zip64 ||= @local_header_offset >= 0xFFFFFFFF unless for_local_header
652
+ if need_zip64
653
+ @version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT_ZIP64
654
+ @extra.delete('Zip64Placeholder')
655
+ zip64 = @extra.create('Zip64')
656
+ if for_local_header
657
+ # local header always includes size and compressed size
658
+ zip64.original_size = @size
659
+ zip64.compressed_size = @compressed_size
660
+ else
661
+ # central directory entry entries include whichever fields are necessary
662
+ zip64.original_size = @size if @size >= 0xFFFFFFFF
663
+ zip64.compressed_size = @compressed_size if @compressed_size >= 0xFFFFFFFF
664
+ zip64.relative_header_offset = @local_header_offset if @local_header_offset >= 0xFFFFFFFF
665
+ end
666
+ else
667
+ @extra.delete('Zip64')
668
+
669
+ # if this is a local header entry, create a placeholder
670
+ # so we have room to write a zip64 extra field afterward
671
+ # (we won't know if it's needed until the file data is written)
672
+ if for_local_header
673
+ @extra.create('Zip64Placeholder')
674
+ else
675
+ @extra.delete('Zip64Placeholder')
676
+ end
677
+ end
678
+ end
617
679
  end
618
680
  end
619
681