archive-zip 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/HACKING +25 -42
- data/NEWS +25 -0
- data/README +2 -2
- data/Rakefile +202 -0
- data/TODO +5 -0
- data/default.mspec +8 -0
- data/lib/archive/support/binary_stringio.rb +23 -0
- data/lib/archive/support/integer.rb +13 -0
- data/lib/archive/support/io-like.rb +3 -1
- data/lib/archive/support/ioextensions.rb +16 -0
- data/lib/archive/support/iowindow.rb +10 -18
- data/lib/archive/support/time.rb +2 -0
- data/lib/archive/support/zlib.rb +298 -71
- data/lib/archive/zip.rb +161 -139
- data/lib/archive/zip/codec.rb +2 -0
- data/lib/archive/zip/codec/deflate.rb +59 -11
- data/lib/archive/zip/codec/null_encryption.rb +75 -14
- data/lib/archive/zip/codec/store.rb +75 -26
- data/lib/archive/zip/codec/traditional_encryption.rb +146 -35
- data/lib/archive/zip/data_descriptor.rb +6 -4
- data/lib/archive/zip/entry.rb +184 -132
- data/lib/archive/zip/error.rb +2 -0
- data/lib/archive/zip/extra_field.rb +20 -6
- data/lib/archive/zip/extra_field/extended_timestamp.rb +141 -60
- data/lib/archive/zip/extra_field/raw.rb +70 -12
- data/lib/archive/zip/extra_field/unix.rb +58 -16
- data/lib/archive/zip/version.rb +6 -0
- data/spec/archive/zip/codec/deflate/compress/checksum_spec.rb +42 -0
- data/spec/archive/zip/codec/deflate/compress/close_spec.rb +44 -0
- data/spec/archive/zip/codec/deflate/compress/crc32_spec.rb +21 -0
- data/spec/archive/zip/codec/deflate/compress/data_descriptor_spec.rb +67 -0
- data/spec/archive/zip/codec/deflate/compress/new_spec.rb +37 -0
- data/spec/archive/zip/codec/deflate/compress/open_spec.rb +46 -0
- data/spec/archive/zip/codec/deflate/compress/write_spec.rb +109 -0
- data/spec/archive/zip/codec/deflate/decompress/checksum_spec.rb +18 -0
- data/spec/archive/zip/codec/deflate/decompress/close_spec.rb +33 -0
- data/spec/archive/zip/codec/deflate/decompress/crc32_spec.rb +18 -0
- data/spec/archive/zip/codec/deflate/decompress/data_descriptor_spec.rb +67 -0
- data/spec/archive/zip/codec/deflate/decompress/new_spec.rb +14 -0
- data/spec/archive/zip/codec/deflate/decompress/open_spec.rb +27 -0
- data/spec/archive/zip/codec/deflate/fixtures/classes.rb +25 -0
- data/spec/archive/zip/codec/deflate/fixtures/compressed_file.bin +1 -0
- data/spec/archive/zip/codec/deflate/fixtures/compressed_file_nocomp.bin +0 -0
- data/spec/archive/zip/codec/deflate/fixtures/raw_file.txt +10 -0
- data/spec/archive/zip/codec/null_encryption/decrypt/close_spec.rb +33 -0
- data/spec/archive/zip/codec/null_encryption/decrypt/new_spec.rb +14 -0
- data/spec/archive/zip/codec/null_encryption/decrypt/open_spec.rb +27 -0
- data/spec/archive/zip/codec/null_encryption/decrypt/read_spec.rb +24 -0
- data/spec/archive/zip/codec/null_encryption/decrypt/rewind_spec.rb +25 -0
- data/spec/archive/zip/codec/null_encryption/decrypt/seek_spec.rb +57 -0
- data/spec/archive/zip/codec/null_encryption/decrypt/tell_spec.rb +21 -0
- data/spec/archive/zip/codec/null_encryption/encrypt/close_spec.rb +33 -0
- data/spec/archive/zip/codec/null_encryption/encrypt/new_spec.rb +14 -0
- data/spec/archive/zip/codec/null_encryption/encrypt/open_spec.rb +27 -0
- data/spec/archive/zip/codec/null_encryption/encrypt/rewind_spec.rb +26 -0
- data/spec/archive/zip/codec/null_encryption/encrypt/seek_spec.rb +50 -0
- data/spec/archive/zip/codec/null_encryption/encrypt/tell_spec.rb +29 -0
- data/spec/archive/zip/codec/null_encryption/encrypt/write_spec.rb +29 -0
- data/spec/archive/zip/codec/null_encryption/fixtures/classes.rb +12 -0
- data/spec/archive/zip/codec/null_encryption/fixtures/raw_file.txt +10 -0
- data/spec/archive/zip/codec/store/compress/close_spec.rb +33 -0
- data/spec/archive/zip/codec/store/compress/data_descriptor_spec.rb +68 -0
- data/spec/archive/zip/codec/store/compress/new_spec.rb +14 -0
- data/spec/archive/zip/codec/store/compress/open_spec.rb +27 -0
- data/spec/archive/zip/codec/store/compress/rewind_spec.rb +26 -0
- data/spec/archive/zip/codec/store/compress/seek_spec.rb +50 -0
- data/spec/archive/zip/codec/store/compress/tell_spec.rb +29 -0
- data/spec/archive/zip/codec/store/compress/write_spec.rb +29 -0
- data/spec/archive/zip/codec/store/decompress/close_spec.rb +33 -0
- data/spec/archive/zip/codec/store/decompress/data_descriptor_spec.rb +68 -0
- data/spec/archive/zip/codec/store/decompress/new_spec.rb +14 -0
- data/spec/archive/zip/codec/store/decompress/open_spec.rb +27 -0
- data/spec/archive/zip/codec/store/decompress/read_spec.rb +24 -0
- data/spec/archive/zip/codec/store/decompress/rewind_spec.rb +25 -0
- data/spec/archive/zip/codec/store/decompress/seek_spec.rb +57 -0
- data/spec/archive/zip/codec/store/decompress/tell_spec.rb +21 -0
- data/spec/archive/zip/codec/store/fixtures/classes.rb +12 -0
- data/spec/archive/zip/codec/store/fixtures/raw_file.txt +10 -0
- data/spec/archive/zip/codec/traditional_encryption/decrypt/close_spec.rb +64 -0
- data/spec/archive/zip/codec/traditional_encryption/decrypt/new_spec.rb +18 -0
- data/spec/archive/zip/codec/traditional_encryption/decrypt/open_spec.rb +39 -0
- data/spec/archive/zip/codec/traditional_encryption/decrypt/read_spec.rb +126 -0
- data/spec/archive/zip/codec/traditional_encryption/decrypt/rewind_spec.rb +38 -0
- data/spec/archive/zip/codec/traditional_encryption/decrypt/seek_spec.rb +82 -0
- data/spec/archive/zip/codec/traditional_encryption/decrypt/tell_spec.rb +25 -0
- data/spec/archive/zip/codec/traditional_encryption/encrypt/close_spec.rb +64 -0
- data/spec/archive/zip/codec/traditional_encryption/encrypt/new_spec.rb +18 -0
- data/spec/archive/zip/codec/traditional_encryption/encrypt/open_spec.rb +39 -0
- data/spec/archive/zip/codec/traditional_encryption/encrypt/rewind_spec.rb +41 -0
- data/spec/archive/zip/codec/traditional_encryption/encrypt/seek_spec.rb +75 -0
- data/spec/archive/zip/codec/traditional_encryption/encrypt/tell_spec.rb +42 -0
- data/spec/archive/zip/codec/traditional_encryption/encrypt/write_spec.rb +127 -0
- data/spec/archive/zip/codec/traditional_encryption/fixtures/classes.rb +27 -0
- data/spec/archive/zip/codec/traditional_encryption/fixtures/encrypted_file.bin +0 -0
- data/spec/archive/zip/codec/traditional_encryption/fixtures/raw_file.txt +10 -0
- data/spec/binary_stringio/new_spec.rb +34 -0
- data/spec/binary_stringio/set_encoding_spec.rb +14 -0
- data/spec/ioextensions/read_exactly_spec.rb +50 -0
- data/spec/zlib/fixtures/classes.rb +65 -0
- data/spec/zlib/fixtures/compressed_file.bin +1 -0
- data/spec/zlib/fixtures/compressed_file_gzip.bin +0 -0
- data/spec/zlib/fixtures/compressed_file_huffman.bin +2 -0
- data/spec/zlib/fixtures/compressed_file_minmem.bin +0 -0
- data/spec/zlib/fixtures/compressed_file_minwin.bin +1 -0
- data/spec/zlib/fixtures/compressed_file_nocomp.bin +0 -0
- data/spec/zlib/fixtures/compressed_file_raw.bin +1 -0
- data/spec/zlib/fixtures/raw_file.txt +10 -0
- data/spec/zlib/zreader/checksum_spec.rb +40 -0
- data/spec/zlib/zreader/close_spec.rb +14 -0
- data/spec/zlib/zreader/compressed_size_spec.rb +18 -0
- data/spec/zlib/zreader/new_spec.rb +41 -0
- data/spec/zlib/zreader/open_spec.rb +49 -0
- data/spec/zlib/zreader/read_spec.rb +47 -0
- data/spec/zlib/zreader/rewind_spec.rb +23 -0
- data/spec/zlib/zreader/seek_spec.rb +55 -0
- data/spec/zlib/zreader/tell_spec.rb +21 -0
- data/spec/zlib/zreader/uncompressed_size_spec.rb +18 -0
- data/spec/zlib/zwriter/checksum_spec.rb +41 -0
- data/spec/zlib/zwriter/close_spec.rb +14 -0
- data/spec/zlib/zwriter/compressed_size_spec.rb +19 -0
- data/spec/zlib/zwriter/new_spec.rb +64 -0
- data/spec/zlib/zwriter/open_spec.rb +68 -0
- data/spec/zlib/zwriter/rewind_spec.rb +26 -0
- data/spec/zlib/zwriter/seek_spec.rb +54 -0
- data/spec/zlib/zwriter/tell_spec.rb +29 -0
- data/spec/zlib/zwriter/uncompressed_size_spec.rb +19 -0
- data/spec/zlib/zwriter/write_spec.rb +28 -0
- data/spec_helper.rb +49 -0
- metadata +296 -74
- data/MANIFEST +0 -27
- data/lib/archive/support/io.rb +0 -14
- 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::
|
|
3
|
-
#
|
|
4
|
-
#
|
|
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
|
|
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(
|
data/lib/archive/zip/entry.rb
CHANGED
|
@@ -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
|
|
201
|
-
#
|
|
202
|
-
#
|
|
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
|
|
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::
|
|
213
|
-
#
|
|
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
|
|
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
|
|
305
|
-
#
|
|
306
|
-
#
|
|
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 =
|
|
319
|
+
cfr.local_header_position =
|
|
320
|
+
IOExtensions.read_exactly(io, 42).unpack('vvvvVVVVvvvvvVV')
|
|
325
321
|
|
|
326
|
-
cfr.zip_path =
|
|
327
|
-
cfr.extra_fields =
|
|
328
|
-
|
|
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
|
|
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
|
|
340
|
-
#
|
|
341
|
-
# 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 =
|
|
357
|
+
extra_fields_length =
|
|
358
|
+
IOExtensions.read_exactly(io, 26).unpack('vvvVVVVvv')
|
|
360
359
|
|
|
361
|
-
lfr.zip_path =
|
|
362
|
-
lfr.extra_fields =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
390
|
+
rescue EOFError
|
|
389
391
|
raise Zip::EntryError, 'unexpected end of file'
|
|
390
392
|
end
|
|
391
393
|
|
|
392
|
-
# Parses the extra fields for
|
|
393
|
-
#
|
|
394
|
-
#
|
|
395
|
-
def self.
|
|
396
|
-
|
|
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
|
-
|
|
400
|
-
|
|
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.
|
|
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::
|
|
478
|
-
#
|
|
479
|
-
#
|
|
480
|
-
#
|
|
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
|
|
525
|
-
#
|
|
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
|
-
|
|
532
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
721
|
-
|
|
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
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
ExtraField::Unix.new(
|
|
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 =
|
|
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_.
|
|
1027
|
-
#
|
|
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
|
-
|
|
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
|