archive-zip 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (132) hide show
  1. data/HACKING +25 -42
  2. data/NEWS +25 -0
  3. data/README +2 -2
  4. data/Rakefile +202 -0
  5. data/TODO +5 -0
  6. data/default.mspec +8 -0
  7. data/lib/archive/support/binary_stringio.rb +23 -0
  8. data/lib/archive/support/integer.rb +13 -0
  9. data/lib/archive/support/io-like.rb +3 -1
  10. data/lib/archive/support/ioextensions.rb +16 -0
  11. data/lib/archive/support/iowindow.rb +10 -18
  12. data/lib/archive/support/time.rb +2 -0
  13. data/lib/archive/support/zlib.rb +298 -71
  14. data/lib/archive/zip.rb +161 -139
  15. data/lib/archive/zip/codec.rb +2 -0
  16. data/lib/archive/zip/codec/deflate.rb +59 -11
  17. data/lib/archive/zip/codec/null_encryption.rb +75 -14
  18. data/lib/archive/zip/codec/store.rb +75 -26
  19. data/lib/archive/zip/codec/traditional_encryption.rb +146 -35
  20. data/lib/archive/zip/data_descriptor.rb +6 -4
  21. data/lib/archive/zip/entry.rb +184 -132
  22. data/lib/archive/zip/error.rb +2 -0
  23. data/lib/archive/zip/extra_field.rb +20 -6
  24. data/lib/archive/zip/extra_field/extended_timestamp.rb +141 -60
  25. data/lib/archive/zip/extra_field/raw.rb +70 -12
  26. data/lib/archive/zip/extra_field/unix.rb +58 -16
  27. data/lib/archive/zip/version.rb +6 -0
  28. data/spec/archive/zip/codec/deflate/compress/checksum_spec.rb +42 -0
  29. data/spec/archive/zip/codec/deflate/compress/close_spec.rb +44 -0
  30. data/spec/archive/zip/codec/deflate/compress/crc32_spec.rb +21 -0
  31. data/spec/archive/zip/codec/deflate/compress/data_descriptor_spec.rb +67 -0
  32. data/spec/archive/zip/codec/deflate/compress/new_spec.rb +37 -0
  33. data/spec/archive/zip/codec/deflate/compress/open_spec.rb +46 -0
  34. data/spec/archive/zip/codec/deflate/compress/write_spec.rb +109 -0
  35. data/spec/archive/zip/codec/deflate/decompress/checksum_spec.rb +18 -0
  36. data/spec/archive/zip/codec/deflate/decompress/close_spec.rb +33 -0
  37. data/spec/archive/zip/codec/deflate/decompress/crc32_spec.rb +18 -0
  38. data/spec/archive/zip/codec/deflate/decompress/data_descriptor_spec.rb +67 -0
  39. data/spec/archive/zip/codec/deflate/decompress/new_spec.rb +14 -0
  40. data/spec/archive/zip/codec/deflate/decompress/open_spec.rb +27 -0
  41. data/spec/archive/zip/codec/deflate/fixtures/classes.rb +25 -0
  42. data/spec/archive/zip/codec/deflate/fixtures/compressed_file.bin +1 -0
  43. data/spec/archive/zip/codec/deflate/fixtures/compressed_file_nocomp.bin +0 -0
  44. data/spec/archive/zip/codec/deflate/fixtures/raw_file.txt +10 -0
  45. data/spec/archive/zip/codec/null_encryption/decrypt/close_spec.rb +33 -0
  46. data/spec/archive/zip/codec/null_encryption/decrypt/new_spec.rb +14 -0
  47. data/spec/archive/zip/codec/null_encryption/decrypt/open_spec.rb +27 -0
  48. data/spec/archive/zip/codec/null_encryption/decrypt/read_spec.rb +24 -0
  49. data/spec/archive/zip/codec/null_encryption/decrypt/rewind_spec.rb +25 -0
  50. data/spec/archive/zip/codec/null_encryption/decrypt/seek_spec.rb +57 -0
  51. data/spec/archive/zip/codec/null_encryption/decrypt/tell_spec.rb +21 -0
  52. data/spec/archive/zip/codec/null_encryption/encrypt/close_spec.rb +33 -0
  53. data/spec/archive/zip/codec/null_encryption/encrypt/new_spec.rb +14 -0
  54. data/spec/archive/zip/codec/null_encryption/encrypt/open_spec.rb +27 -0
  55. data/spec/archive/zip/codec/null_encryption/encrypt/rewind_spec.rb +26 -0
  56. data/spec/archive/zip/codec/null_encryption/encrypt/seek_spec.rb +50 -0
  57. data/spec/archive/zip/codec/null_encryption/encrypt/tell_spec.rb +29 -0
  58. data/spec/archive/zip/codec/null_encryption/encrypt/write_spec.rb +29 -0
  59. data/spec/archive/zip/codec/null_encryption/fixtures/classes.rb +12 -0
  60. data/spec/archive/zip/codec/null_encryption/fixtures/raw_file.txt +10 -0
  61. data/spec/archive/zip/codec/store/compress/close_spec.rb +33 -0
  62. data/spec/archive/zip/codec/store/compress/data_descriptor_spec.rb +68 -0
  63. data/spec/archive/zip/codec/store/compress/new_spec.rb +14 -0
  64. data/spec/archive/zip/codec/store/compress/open_spec.rb +27 -0
  65. data/spec/archive/zip/codec/store/compress/rewind_spec.rb +26 -0
  66. data/spec/archive/zip/codec/store/compress/seek_spec.rb +50 -0
  67. data/spec/archive/zip/codec/store/compress/tell_spec.rb +29 -0
  68. data/spec/archive/zip/codec/store/compress/write_spec.rb +29 -0
  69. data/spec/archive/zip/codec/store/decompress/close_spec.rb +33 -0
  70. data/spec/archive/zip/codec/store/decompress/data_descriptor_spec.rb +68 -0
  71. data/spec/archive/zip/codec/store/decompress/new_spec.rb +14 -0
  72. data/spec/archive/zip/codec/store/decompress/open_spec.rb +27 -0
  73. data/spec/archive/zip/codec/store/decompress/read_spec.rb +24 -0
  74. data/spec/archive/zip/codec/store/decompress/rewind_spec.rb +25 -0
  75. data/spec/archive/zip/codec/store/decompress/seek_spec.rb +57 -0
  76. data/spec/archive/zip/codec/store/decompress/tell_spec.rb +21 -0
  77. data/spec/archive/zip/codec/store/fixtures/classes.rb +12 -0
  78. data/spec/archive/zip/codec/store/fixtures/raw_file.txt +10 -0
  79. data/spec/archive/zip/codec/traditional_encryption/decrypt/close_spec.rb +64 -0
  80. data/spec/archive/zip/codec/traditional_encryption/decrypt/new_spec.rb +18 -0
  81. data/spec/archive/zip/codec/traditional_encryption/decrypt/open_spec.rb +39 -0
  82. data/spec/archive/zip/codec/traditional_encryption/decrypt/read_spec.rb +126 -0
  83. data/spec/archive/zip/codec/traditional_encryption/decrypt/rewind_spec.rb +38 -0
  84. data/spec/archive/zip/codec/traditional_encryption/decrypt/seek_spec.rb +82 -0
  85. data/spec/archive/zip/codec/traditional_encryption/decrypt/tell_spec.rb +25 -0
  86. data/spec/archive/zip/codec/traditional_encryption/encrypt/close_spec.rb +64 -0
  87. data/spec/archive/zip/codec/traditional_encryption/encrypt/new_spec.rb +18 -0
  88. data/spec/archive/zip/codec/traditional_encryption/encrypt/open_spec.rb +39 -0
  89. data/spec/archive/zip/codec/traditional_encryption/encrypt/rewind_spec.rb +41 -0
  90. data/spec/archive/zip/codec/traditional_encryption/encrypt/seek_spec.rb +75 -0
  91. data/spec/archive/zip/codec/traditional_encryption/encrypt/tell_spec.rb +42 -0
  92. data/spec/archive/zip/codec/traditional_encryption/encrypt/write_spec.rb +127 -0
  93. data/spec/archive/zip/codec/traditional_encryption/fixtures/classes.rb +27 -0
  94. data/spec/archive/zip/codec/traditional_encryption/fixtures/encrypted_file.bin +0 -0
  95. data/spec/archive/zip/codec/traditional_encryption/fixtures/raw_file.txt +10 -0
  96. data/spec/binary_stringio/new_spec.rb +34 -0
  97. data/spec/binary_stringio/set_encoding_spec.rb +14 -0
  98. data/spec/ioextensions/read_exactly_spec.rb +50 -0
  99. data/spec/zlib/fixtures/classes.rb +65 -0
  100. data/spec/zlib/fixtures/compressed_file.bin +1 -0
  101. data/spec/zlib/fixtures/compressed_file_gzip.bin +0 -0
  102. data/spec/zlib/fixtures/compressed_file_huffman.bin +2 -0
  103. data/spec/zlib/fixtures/compressed_file_minmem.bin +0 -0
  104. data/spec/zlib/fixtures/compressed_file_minwin.bin +1 -0
  105. data/spec/zlib/fixtures/compressed_file_nocomp.bin +0 -0
  106. data/spec/zlib/fixtures/compressed_file_raw.bin +1 -0
  107. data/spec/zlib/fixtures/raw_file.txt +10 -0
  108. data/spec/zlib/zreader/checksum_spec.rb +40 -0
  109. data/spec/zlib/zreader/close_spec.rb +14 -0
  110. data/spec/zlib/zreader/compressed_size_spec.rb +18 -0
  111. data/spec/zlib/zreader/new_spec.rb +41 -0
  112. data/spec/zlib/zreader/open_spec.rb +49 -0
  113. data/spec/zlib/zreader/read_spec.rb +47 -0
  114. data/spec/zlib/zreader/rewind_spec.rb +23 -0
  115. data/spec/zlib/zreader/seek_spec.rb +55 -0
  116. data/spec/zlib/zreader/tell_spec.rb +21 -0
  117. data/spec/zlib/zreader/uncompressed_size_spec.rb +18 -0
  118. data/spec/zlib/zwriter/checksum_spec.rb +41 -0
  119. data/spec/zlib/zwriter/close_spec.rb +14 -0
  120. data/spec/zlib/zwriter/compressed_size_spec.rb +19 -0
  121. data/spec/zlib/zwriter/new_spec.rb +64 -0
  122. data/spec/zlib/zwriter/open_spec.rb +68 -0
  123. data/spec/zlib/zwriter/rewind_spec.rb +26 -0
  124. data/spec/zlib/zwriter/seek_spec.rb +54 -0
  125. data/spec/zlib/zwriter/tell_spec.rb +29 -0
  126. data/spec/zlib/zwriter/uncompressed_size_spec.rb +19 -0
  127. data/spec/zlib/zwriter/write_spec.rb +28 -0
  128. data/spec_helper.rb +49 -0
  129. metadata +296 -74
  130. data/MANIFEST +0 -27
  131. data/lib/archive/support/io.rb +0 -14
  132. data/lib/archive/support/stringio.rb +0 -22
@@ -1,7 +1,9 @@
1
+ # encoding: UTF-8
2
+
1
3
  module Archive; class Zip
2
- # Archive::Zip::Entry::DataDescriptor is a convenience class which bundles
3
- # imporant information concerning the compressed data in a ZIP archive entry
4
- # and allows easy comparisons between instances of itself.
4
+ # Archive::Zip::DataDescriptor is a convenience class which bundles important
5
+ # information concerning the compressed data in a ZIP archive entry and allows
6
+ # easy comparisons between instances of itself.
5
7
  class DataDescriptor
6
8
  # Create a new instance of this class where <em>crc32</em>,
7
9
  # _compressed_size_, and _uncompressed_size_ are all integers representing a
@@ -41,7 +43,7 @@ module Archive; class Zip
41
43
  end
42
44
 
43
45
  # Writes the data wrapped in this object to _io_ which must be a writable,
44
- # IO-like object prividing a _write_ method. Returns the number of bytes
46
+ # IO-like object providing a _write_ method. Returns the number of bytes
45
47
  # written.
46
48
  def dump(io)
47
49
  io.write(
@@ -1,3 +1,7 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'archive/support/ioextensions'
4
+ require 'archive/support/binary_stringio'
1
5
  require 'archive/zip/codec/deflate'
2
6
  require 'archive/zip/codec/null_encryption'
3
7
  require 'archive/zip/codec/store'
@@ -197,33 +201,26 @@ module Archive; class Zip
197
201
  end
198
202
 
199
203
  # Creates and returns a new entry object by parsing from the current
200
- # position of _io_. _io_ must be a readable, IO-like object which provides
201
- # a _readbytes_ method, and it must be positioned at the start of a central
202
- # file record following the signature for that record.
204
+ # position of _io_. _io_ must be a readable, IO-like object which is
205
+ # positioned at the start of a central file record following the signature
206
+ # for that record.
203
207
  #
204
- # <b>NOTE:</b> For now _io_ MUST be seekable and report such by returning
205
- # +true+ from its <i>seekable?</i> method. See IO#seekable?.
208
+ # <b>NOTE:</b> For now _io_ MUST be seekable.
206
209
  #
207
210
  # Currently, the only entry objects returned are instances of
208
211
  # Archive::Zip::Entry::File, Archive::Zip::Entry::Directory, and
209
212
  # Archive::Zip::Entry::Symlink. Any other kind of entry will be mapped into
210
213
  # an instance of Archive::Zip::Entry::File.
211
214
  #
212
- # Raises Archive::Zip::IOError if _io_ is not seekable. Raises
213
- # Archive::Zip::EntryError for any other errors related to processing the
214
- # entry.
215
+ # Raises Archive::Zip::EntryError for any other errors related to processing
216
+ # the entry.
215
217
  def self.parse(io)
216
- # Error out if the IO object is not confirmed seekable.
217
- unless io.respond_to?(:seekable?) and io.seekable? then
218
- raise Zip::IOError, 'non-seekable IO object given'
219
- end
220
-
221
218
  # Parse the central file record and then use the information found there
222
219
  # to locate and parse the corresponding local file record.
223
220
  cfr = parse_central_file_record(io)
224
221
  next_record_position = io.pos
225
222
  io.seek(cfr.local_header_position)
226
- unless io.readbytes(4) == LFH_SIGNATURE then
223
+ unless IOExtensions.read_exactly(io, 4) == LFH_SIGNATURE then
227
224
  raise Zip::EntryError, 'bad local file header signature'
228
225
  end
229
226
  lfr = parse_local_file_record(io, cfr.compressed_size)
@@ -275,8 +272,6 @@ module Archive; class Zip
275
272
 
276
273
  # Set the expected data descriptor so that extraction can be verified.
277
274
  entry.expected_data_descriptor = expected_data_descriptor
278
- # Record the raw file data for the entry.
279
- entry.raw_data = IOWindow.new(io, io.pos, cfr.compressed_size)
280
275
  # Record the compression codec.
281
276
  entry.compression_codec = compression_codec
282
277
  # Record the encryption codec.
@@ -301,9 +296,9 @@ module Archive; class Zip
301
296
  private
302
297
 
303
298
  # Parses a central file record and returns a CFHRecord instance containing
304
- # the parsed data. _io_ must be a readable, IO-like object which provides a
305
- # _readbytes_ method, and it must be positioned at the start of a central
306
- # file record following the signature for that record.
299
+ # the parsed data. _io_ must be a readable, IO-like object which is
300
+ # positioned at the start of a central file record following the signature
301
+ # for that record.
307
302
  def self.parse_central_file_record(io)
308
303
  cfr = CFHRecord.new
309
304
 
@@ -321,24 +316,27 @@ module Archive; class Zip
321
316
  cfr.disk_number_start,
322
317
  cfr.internal_file_attributes,
323
318
  cfr.external_file_attributes,
324
- cfr.local_header_position = io.readbytes(42).unpack('vvvvVVVVvvvvvVV')
319
+ cfr.local_header_position =
320
+ IOExtensions.read_exactly(io, 42).unpack('vvvvVVVVvvvvvVV')
325
321
 
326
- cfr.zip_path = io.readbytes(file_name_length)
327
- cfr.extra_fields = parse_extra_fields(io.readbytes(extra_fields_length))
328
- cfr.comment = io.readbytes(comment_length)
322
+ cfr.zip_path = IOExtensions.read_exactly(io, file_name_length)
323
+ cfr.extra_fields = parse_central_extra_fields(
324
+ IOExtensions.read_exactly(io, extra_fields_length)
325
+ )
326
+ cfr.comment = IOExtensions.read_exactly(io, comment_length)
329
327
 
330
328
  # Convert from MSDOS time to Unix time.
331
329
  cfr.mtime = DOSTime.new(dos_mtime).to_time
332
330
 
333
331
  cfr
334
- rescue EOFError, TruncatedDataError
332
+ rescue EOFError
335
333
  raise Zip::EntryError, 'unexpected end of file'
336
334
  end
337
335
 
338
336
  # Parses a local file record and returns a LFHRecord instance containing the
339
- # parsed data. _io_ must be a readable, IO-like object which provides a
340
- # readbytes method, and it must be positioned at the start of a local file
341
- # record following the signature for that record.
337
+ # parsed data. _io_ must be a readable, IO-like object which is positioned
338
+ # at the start of a local file record following the signature for that
339
+ # record.
342
340
  #
343
341
  # If the record to be parsed is flagged to have a trailing data descriptor
344
342
  # record, _expected_compressed_size_ must be set to an integer counting the
@@ -356,10 +354,13 @@ module Archive; class Zip
356
354
  lfr.compressed_size,
357
355
  lfr.uncompressed_size,
358
356
  file_name_length,
359
- extra_fields_length = io.readbytes(26).unpack('vvvVVVVvv')
357
+ extra_fields_length =
358
+ IOExtensions.read_exactly(io, 26).unpack('vvvVVVVvv')
360
359
 
361
- lfr.zip_path = io.readbytes(file_name_length)
362
- lfr.extra_fields = parse_extra_fields(io.readbytes(extra_fields_length))
360
+ lfr.zip_path = IOExtensions.read_exactly(io, file_name_length)
361
+ lfr.extra_fields = parse_local_extra_fields(
362
+ IOExtensions.read_exactly(io, extra_fields_length)
363
+ )
363
364
 
364
365
  # Convert from MSDOS time to Unix time.
365
366
  lfr.mtime = DOSTime.new(dos_mtime).to_time
@@ -371,35 +372,60 @@ module Archive; class Zip
371
372
  # libraries create trailing data descriptor records with a preceding
372
373
  # signature while others do not.
373
374
  # This handles both cases.
374
- possible_signature = io.readbytes(4)
375
+ possible_signature = IOExtensions.read_exactly(io, 4)
375
376
  if possible_signature == DD_SIGNATURE then
376
377
  lfr.crc32,
377
378
  lfr.compressed_size,
378
- lfr.uncompressed_size = io.readbytes(12).unpack('VVV')
379
+ lfr.uncompressed_size =
380
+ IOExtensions.read_exactly(io, 12).unpack('VVV')
379
381
  else
380
382
  lfr.crc32 = possible_signature.unpack('V')[0]
381
383
  lfr.compressed_size,
382
- lfr.uncompressed_size = io.readbytes(8).unpack('VV')
384
+ lfr.uncompressed_size = IOExtensions.read_exactly(io, 8).unpack('VV')
383
385
  end
384
386
  io.pos = saved_pos
385
387
  end
386
388
 
387
389
  lfr
388
- rescue EOFError, TruncatedDataError
390
+ rescue EOFError
389
391
  raise Zip::EntryError, 'unexpected end of file'
390
392
  end
391
393
 
392
- # Parses the extra fields for local and central file records and returns an
393
- # array of extra field objects. _bytes_ must be a String containing all of
394
- # the extra field data to be parsed.
395
- def self.parse_extra_fields(bytes)
396
- StringIO.open(bytes) do |io|
394
+ # Parses the extra fields for central file records and returns an array of
395
+ # extra field objects. _bytes_ must be a String containing all of the extra
396
+ # field data to be parsed.
397
+ def self.parse_central_extra_fields(bytes)
398
+ BinaryStringIO.open(bytes) do |io|
397
399
  extra_fields = []
398
400
  while ! io.eof? do
399
- header_id, data_size = io.readbytes(4).unpack('vv')
400
- data = io.readbytes(data_size)
401
+ begin
402
+ header_id, data_size = IOExtensions.read_exactly(io, 4).unpack('vv')
403
+ data = IOExtensions.read_exactly(io, data_size)
404
+ rescue ::EOFError
405
+ raise EntryError, 'insufficient data available'
406
+ end
407
+
408
+ extra_fields << ExtraField.parse_central(header_id, data)
409
+ end
410
+ extra_fields
411
+ end
412
+ end
413
+
414
+ # Parses the extra fields for local file records and returns an array of
415
+ # extra field objects. _bytes_ must be a String containing all of the extra
416
+ # field data to be parsed.
417
+ def self.parse_local_extra_fields(bytes)
418
+ BinaryStringIO.open(bytes) do |io|
419
+ extra_fields = []
420
+ while ! io.eof? do
421
+ begin
422
+ header_id, data_size = IOExtensions.read_exactly(io, 4).unpack('vv')
423
+ data = IOExtensions.read_exactly(io, data_size)
424
+ rescue ::EOFError
425
+ raise EntryError, 'insufficient data available'
426
+ end
401
427
 
402
- extra_fields << ExtraField.parse(header_id, data)
428
+ extra_fields << ExtraField.parse_local(header_id, data)
403
429
  end
404
430
  extra_fields
405
431
  end
@@ -474,10 +500,10 @@ module Archive; class Zip
474
500
  attr_accessor :mode
475
501
  # The comment associated with this entry.
476
502
  attr_accessor :comment
477
- # An Archive::Zip::Entry::DataDescriptor instance which should contain the
478
- # expected CRC32 checksum, compressed size, and uncompressed size for the
479
- # file data. When not +nil+, this is used by #extract to confirm that the
480
- # data extraction was successful.
503
+ # An Archive::Zip::DataDescriptor instance which should contain the expected
504
+ # CRC32 checksum, compressed size, and uncompressed size for the file data.
505
+ # When not +nil+, this is used by #extract to confirm that the data
506
+ # extraction was successful.
481
507
  attr_accessor :expected_data_descriptor
482
508
  # The selected compression codec.
483
509
  attr_accessor :compression_codec
@@ -521,24 +547,37 @@ module Archive; class Zip
521
547
  false
522
548
  end
523
549
 
524
- # Adds _extra_field_ as an extra field specification to this entry. If
525
- # _extra_field_ is an instance of
550
+ # Adds _extra_field_ as an extra field specification to *both* the central
551
+ # file record and the local file record of this entry.
552
+ #
553
+ # If _extra_field_ is an instance of
526
554
  # Archive::Zip::Entry::ExtraField::ExtendedTimestamp, the values of that
527
555
  # field are used to set mtime and atime for this entry. If _extra_field_ is
528
556
  # an instance of Archive::Zip::Entry::ExtraField::Unix, the values of that
529
557
  # field are used to set mtime, atime, uid, and gid for this entry.
530
558
  def add_extra_field(extra_field)
531
- @extra_field_data = nil
532
- @extra_fields << extra_field
559
+ # Try to find an extra field with the same header ID already in the list
560
+ # and merge the new one with that if one exists; otherwise, add the new
561
+ # one to the list.
562
+ existing_extra_field = @extra_fields.find do |ef|
563
+ ef.header_id == extra_field.header_id
564
+ end
565
+ if existing_extra_field.nil? then
566
+ @extra_fields << extra_field
567
+ else
568
+ extra_field = existing_extra_field.merge(extra_field)
569
+ end
533
570
 
571
+ # Set some attributes of this entry based on the settings in select types
572
+ # of extra fields.
534
573
  if extra_field.kind_of?(ExtraField::ExtendedTimestamp) then
535
- self.mtime = extra_field.mtime
536
- self.atime = extra_field.atime
574
+ self.mtime = extra_field.mtime unless extra_field.mtime.nil?
575
+ self.atime = extra_field.atime unless extra_field.atime.nil?
537
576
  elsif extra_field.kind_of?(ExtraField::Unix) then
538
- self.mtime = extra_field.mtime
539
- self.atime = extra_field.atime
540
- self.uid = extra_field.uid
541
- self.gid = extra_field.gid
577
+ self.mtime = extra_field.mtime unless extra_field.mtime.nil?
578
+ self.atime = extra_field.atime unless extra_field.atime.nil?
579
+ self.uid = extra_field.uid unless extra_field.uid.nil?
580
+ self.gid = extra_field.gid unless extra_field.uid.nil?
542
581
  end
543
582
  self
544
583
  end
@@ -554,21 +593,34 @@ module Archive; class Zip
554
593
  @local_file_record_position = local_file_record_position
555
594
  bytes_written = 0
556
595
 
596
+ # Assume that no trailing data descriptor will be necessary.
597
+ need_trailing_data_descriptor = false
598
+ begin
599
+ io.pos
600
+ rescue Errno::ESPIPE
601
+ # A trailing data descriptor is required for non-seekable IO.
602
+ need_trailing_data_descriptor = true
603
+ end
604
+ if encryption_codec.class == Codec::TraditionalEncryption then
605
+ # HACK:
606
+ # According to the ZIP specification, a trailing data descriptor should
607
+ # only be required when writing to non-seekable IO , but InfoZIP
608
+ # *always* does this when using traditional encryption even though it
609
+ # will also write the data descriptor in the usual place if possible.
610
+ # Failure to emulate InfoZIP in this behavior will prevent InfoZIP
611
+ # compatibility with traditionally encrypted entries.
612
+ need_trailing_data_descriptor = true
613
+ # HACK:
614
+ # The InfoZIP implementation of traditional encryption requires that the
615
+ # the last modified file time be used as part of the encryption header.
616
+ # This is a deviation from the ZIP specification.
617
+ encryption_codec.mtime = mtime
618
+ end
619
+
620
+ # Set the general purpose flags.
557
621
  general_purpose_flags = compression_codec.general_purpose_flags
558
622
  general_purpose_flags |= encryption_codec.general_purpose_flags
559
-
560
- if ! io.seekable? ||
561
- encryption_codec.class == Codec::TraditionalEncryption then
562
- # Flag that the data descriptor record will follow the compressed file
563
- # data of this entry.
564
- #
565
- # HACK:
566
- # According to the ZIP specification, this should only be done if the IO
567
- # object cannot be accessed randomly, but InfoZIP *always* sets this
568
- # flag when using traditional encryption even though it will also write
569
- # the data descriptor in the usual place if possible. Failure to
570
- # emulate InfoZIP in this behavior will prevent InfoZIP compatibility
571
- # with traditionally encrypted entries.
623
+ if need_trailing_data_descriptor then
572
624
  general_purpose_flags |= FLAG_DATA_DESCRIPTOR_FOLLOWS
573
625
  end
574
626
 
@@ -579,7 +631,9 @@ module Archive; class Zip
579
631
  version_needed_to_extract = encryption_codec.version_needed_to_extract
580
632
  end
581
633
 
634
+ # Write the data.
582
635
  bytes_written += io.write(LFH_SIGNATURE)
636
+ extra_field_data = local_extra_field_data
583
637
  bytes_written += io.write(
584
638
  [
585
639
  version_needed_to_extract,
@@ -596,15 +650,6 @@ module Archive; class Zip
596
650
  bytes_written += io.write(zip_path)
597
651
  bytes_written += io.write(extra_field_data)
598
652
 
599
- if encryption_codec.class == Codec::TraditionalEncryption then
600
- # HACK:
601
- # Traditional encryption requires that the last 2 bytes of the header it
602
- # uses are the 2 low order bytes of the last modified file time in DOS
603
- # format. This is different than stated in the ZIP specification and
604
- # rather comes from the InfoZIP implementation.
605
- encryption_codec.mtime = mtime
606
- end
607
-
608
653
  # Pipeline a compressor into an encryptor, write all the file data to the
609
654
  # compressor, and get a data descriptor from it.
610
655
  encryption_codec.encryptor(io, password) do |e|
@@ -619,31 +664,23 @@ module Archive; class Zip
619
664
  end
620
665
  e.close(false)
621
666
  end
622
-
623
667
  bytes_written += @data_descriptor.compressed_size
624
- if io.seekable? then
668
+
669
+ # Write the trailing data descriptor if necessary.
670
+ if need_trailing_data_descriptor then
671
+ bytes_written += io.write(DD_SIGNATURE)
672
+ bytes_written += @data_descriptor.dump(io)
673
+ end
674
+
675
+ begin
625
676
  # Update the data descriptor located before the compressed data for the
626
677
  # entry.
627
678
  saved_position = io.pos
628
679
  io.pos = @local_file_record_position + 14
629
680
  @data_descriptor.dump(io)
630
681
  io.pos = saved_position
631
- end
632
-
633
- if ! io.seekable? ||
634
- encryption_codec.class == Codec::TraditionalEncryption then
635
- # Write the data descriptor after the compressed file data for the
636
- # entry.
637
- #
638
- # HACK:
639
- # According to the ZIP specification, this should only be done if the IO
640
- # object cannot be accessed randomly, but InfoZIP *always* does this
641
- # when using traditional encryption even though it will also write the
642
- # data descriptor in the usual place if possible. Failure to emulate
643
- # InfoZIP in this behavior will prevent InfoZIP compatibility with
644
- # traditionally encrypted entries.
645
- bytes_written += io.write(DD_SIGNATURE)
646
- bytes_written += @data_descriptor.dump(io)
682
+ rescue Errno::ESPIPE
683
+ # Ignore a failed attempt to update the data descriptor.
647
684
  end
648
685
 
649
686
  bytes_written
@@ -657,21 +694,29 @@ module Archive; class Zip
657
694
  def dump_central_file_record(io)
658
695
  bytes_written = 0
659
696
 
697
+ # Assume that no trailing data descriptor will be necessary.
698
+ need_trailing_data_descriptor = false
699
+ begin
700
+ io.pos
701
+ rescue Errno::ESPIPE
702
+ # A trailing data descriptor is required for non-seekable IO.
703
+ need_trailing_data_descriptor = true
704
+ end
705
+ if encryption_codec.class == Codec::TraditionalEncryption then
706
+ # HACK:
707
+ # According to the ZIP specification, a trailing data descriptor should
708
+ # only be required when writing to non-seekable IO , but InfoZIP
709
+ # *always* does this when using traditional encryption even though it
710
+ # will also write the data descriptor in the usual place if possible.
711
+ # Failure to emulate InfoZIP in this behavior will prevent InfoZIP
712
+ # compatibility with traditionally encrypted entries.
713
+ need_trailing_data_descriptor = true
714
+ end
715
+
716
+ # Set the general purpose flags.
660
717
  general_purpose_flags = compression_codec.general_purpose_flags
661
718
  general_purpose_flags |= encryption_codec.general_purpose_flags
662
-
663
- if ! io.seekable? ||
664
- encryption_codec.class == Codec::TraditionalEncryption then
665
- # Flag that the data descriptor record will follow the compressed file
666
- # data of this entry.
667
- #
668
- # HACK:
669
- # According to the ZIP specification, this should only be done if the IO
670
- # object cannot be accessed randomly, but InfoZIP *always* sets this
671
- # flag when using traditional encryption even though it will also write
672
- # the data descriptor in the usual place if possible. Failure to
673
- # emulate InfoZIP in this behavior will prevent InfoZIP compatibility
674
- # with encrypted entries.
719
+ if need_trailing_data_descriptor then
675
720
  general_purpose_flags |= FLAG_DATA_DESCRIPTOR_FOLLOWS
676
721
  end
677
722
 
@@ -682,6 +727,7 @@ module Archive; class Zip
682
727
  version_needed_to_extract = encryption_codec.version_needed_to_extract
683
728
  end
684
729
 
730
+ # Write the data.
685
731
  bytes_written += io.write(CFH_SIGNATURE)
686
732
  bytes_written += io.write(
687
733
  [
@@ -693,6 +739,7 @@ module Archive; class Zip
693
739
  ].pack('vvvvV')
694
740
  )
695
741
  bytes_written += @data_descriptor.dump(io)
742
+ extra_field_data = central_extra_field_data
696
743
  bytes_written += io.write(
697
744
  [
698
745
  zip_path.length,
@@ -717,19 +764,31 @@ module Archive; class Zip
717
764
  0x0314
718
765
  end
719
766
 
720
- def extra_field_data
721
- return @extra_field_data unless @extra_field_data.nil?
767
+ def central_extra_field_data
768
+ @central_extra_field_data = @extra_fields.collect do |extra_field|
769
+ extra_field.dump_central
770
+ end.join
771
+ end
722
772
 
723
- @extra_field_data = @extra_fields.collect do |extra_field|
724
- unless extra_field.kind_of?(ExtraField::ExtendedTimestamp) ||
725
- extra_field.kind_of?(ExtraField::Unix) then
726
- extra_field.dump
727
- else
728
- ''
729
- end
730
- end.join +
731
- ExtraField::ExtendedTimestamp.new(mtime, atime, nil).dump +
732
- ExtraField::Unix.new(mtime, atime, uid, gid).dump
773
+ def dummy
774
+ # Add fields for time data if available.
775
+ unless mtime.nil? && atime.nil? then
776
+ @central_extra_field_data +=
777
+ ExtraField::ExtendedTimestamp.new(mtime, atime, nil).dump_central
778
+ end
779
+
780
+ # Add fields for user and group ownerships if available.
781
+ unless uid.nil? || gid.nil? || mtime.nil? || atime.nil? then
782
+ @central_extra_field_data += ExtraField::Unix.new(
783
+ mtime, atime, uid, gid
784
+ ).dump_central
785
+ end
786
+ end
787
+
788
+ def local_extra_field_data
789
+ @local_extra_field_data = @extra_fields.collect do |extra_field|
790
+ extra_field.dump_local
791
+ end.join
733
792
  end
734
793
 
735
794
  def internal_file_attributes
@@ -1012,7 +1071,7 @@ module Archive; class Zip; module Entry
1012
1071
  )
1013
1072
  else
1014
1073
  if @file_path.nil? then
1015
- simulated_raw_data = StringIO.new
1074
+ simulated_raw_data = BinaryStringIO.new
1016
1075
  else
1017
1076
  simulated_raw_data = ::File.new(@file_path, 'rb')
1018
1077
  end
@@ -1023,22 +1082,15 @@ module Archive; class Zip; module Entry
1023
1082
  @file_data
1024
1083
  end
1025
1084
 
1026
- # Sets the +file_data+ attribute of this object to _file_data_. If
1027
- # _file_data_ is a String, it will be wrapped in a StringIO instance;
1028
- # otherwise, _file_data_ must be a readable, IO-like object. _file_data_ is
1029
- # then wrapped inside an Archive::Zip::Codec::Store::Unstore instance before
1030
- # finally setting the +file_data+ attribute.
1085
+ # Sets the +file_data+ attribute of this object to _file_data_. _file_data_
1086
+ # must be a readable, IO-like object.
1031
1087
  #
1032
1088
  # <b>NOTE:</b> As a side effect, the +file_path+ and +raw_data+ attributes
1033
1089
  # for this object will be set to +nil+.
1034
1090
  def file_data=(file_data)
1035
1091
  @file_path = nil
1036
1092
  self.raw_data = nil
1037
- if file_data.kind_of?(String)
1038
- @file_data = StringIO.new(file_data)
1039
- else
1040
- @file_data = file_data
1041
- end
1093
+ @file_data = file_data
1042
1094
  # Ensure that the IO-like object can return CRC32 and data size
1043
1095
  # information so that it's possible to verify extraction later if desired.
1044
1096
  unless @file_data.respond_to?(:data_descriptor) then