archive-zip 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/MANIFEST +7 -6
- data/NEWS +17 -7
- data/README +22 -1
- data/lib/archive/zip.rb +105 -36
- data/lib/archive/zip/codec.rb +35 -14
- data/lib/archive/zip/codec/deflate.rb +34 -32
- data/lib/archive/zip/codec/null_encryption.rb +176 -0
- data/lib/archive/zip/codec/store.rb +46 -40
- data/lib/archive/zip/codec/traditional_encryption.rb +308 -0
- data/lib/archive/zip/{datadescriptor.rb → data_descriptor.rb} +8 -6
- data/lib/archive/zip/entry.rb +290 -135
- data/lib/archive/zip/{extrafield.rb → extra_field.rb} +3 -3
- data/lib/archive/zip/{extrafield/extendedtimestamp.rb → extra_field/extended_timestamp.rb} +0 -0
- data/lib/archive/zip/{extrafield → extra_field}/raw.rb +0 -0
- data/lib/archive/zip/{extrafield → extra_field}/unix.rb +0 -0
- metadata +9 -8
- data/test/test_archive.rb +0 -8
@@ -21,17 +21,19 @@ module Archive; class Zip
|
|
21
21
|
# A count of the number of bytes of a set of uncompressed data.
|
22
22
|
attr_reader :uncompressed_size
|
23
23
|
|
24
|
-
# Compares the attributes of this object with
|
25
|
-
# _other_ and raises Archive::Zip::Error for any
|
24
|
+
# Compares the crc32 and uncompressed_size attributes of this object with
|
25
|
+
# like-named attributes of _other_ and raises Archive::Zip::Error for any
|
26
|
+
# mismatches.
|
27
|
+
#
|
28
|
+
# NOTE: The compressed_size attribute is not checked because encrypted
|
29
|
+
# entries may have misleading compressed sizes. Checking only the CRC32 and
|
30
|
+
# uncompressed size of the data should be sufficient to ensure that an entry
|
31
|
+
# has been successfully extracted.
|
26
32
|
def verify(other)
|
27
33
|
unless crc32 == other.crc32 then
|
28
34
|
raise Zip::Error,
|
29
35
|
"CRC32 mismatch: #{crc32.to_s(16)} vs. #{other.crc32.to_s(16)}"
|
30
36
|
end
|
31
|
-
unless compressed_size == other.compressed_size then
|
32
|
-
raise Zip::Error,
|
33
|
-
"compressed size mismatch: #{compressed_size} vs. #{other.compressed_size}"
|
34
|
-
end
|
35
37
|
unless uncompressed_size == other.uncompressed_size then
|
36
38
|
raise Zip::Error,
|
37
39
|
"uncompressed size mismatch: #{uncompressed_size} vs. #{other.uncompressed_size}"
|
data/lib/archive/zip/entry.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
|
+
require 'archive/zip/codec/deflate'
|
2
|
+
require 'archive/zip/codec/null_encryption'
|
1
3
|
require 'archive/zip/codec/store'
|
4
|
+
require 'archive/zip/codec/traditional_encryption'
|
5
|
+
require 'archive/zip/data_descriptor'
|
2
6
|
require 'archive/zip/error'
|
3
|
-
require 'archive/zip/
|
4
|
-
require 'archive/zip/datadescriptor'
|
7
|
+
require 'archive/zip/extra_field'
|
5
8
|
|
6
9
|
module Archive; class Zip
|
7
10
|
# The Archive::Zip::Entry mixin provides classes with methods implementing
|
@@ -12,8 +15,8 @@ module Archive; class Zip
|
|
12
15
|
# overridden to provide sensible information for the new entry type.
|
13
16
|
#
|
14
17
|
# A class using this mixin must provide 2 methods: _extract_ and
|
15
|
-
#
|
16
|
-
#
|
18
|
+
# _dump_file_data_. _extract_ should be a public method with the following
|
19
|
+
# signature:
|
17
20
|
#
|
18
21
|
# def extract(options = {})
|
19
22
|
# ...
|
@@ -26,10 +29,9 @@ module Archive; class Zip
|
|
26
29
|
# Archive::Zip::Entry::Directory#extract for examples of the options currently
|
27
30
|
# supported.
|
28
31
|
#
|
29
|
-
#
|
30
|
-
# signature:
|
32
|
+
# _dump_file_data_ should be a private method with the following signature:
|
31
33
|
#
|
32
|
-
# def
|
34
|
+
# def dump_file_data(io)
|
33
35
|
# ...
|
34
36
|
# end
|
35
37
|
#
|
@@ -74,6 +76,10 @@ module Archive; class Zip
|
|
74
76
|
:compressed_data
|
75
77
|
)
|
76
78
|
|
79
|
+
# When this flag is set in the general purpose flags, it indicates that the
|
80
|
+
# entry's file data is encrypted using the original (weak) algorithm.
|
81
|
+
FLAG_ENCRYPTED = 0b0001
|
82
|
+
|
77
83
|
# When this flag is set in the general purpose flags, it indicates that the
|
78
84
|
# read data descriptor record for a local file record is located after the
|
79
85
|
# entry's file data.
|
@@ -108,9 +114,18 @@ module Archive; class Zip
|
|
108
114
|
# <b>:follow_symlinks</b>::
|
109
115
|
# When set to +true+ (the default), symlinks are treated as the files or
|
110
116
|
# directories to which they point.
|
111
|
-
# <b>:
|
112
|
-
#
|
113
|
-
#
|
117
|
+
# <b>:compression_codec</b>::
|
118
|
+
# Specifies a proc, lambda, or class. If a proc or lambda is used, it
|
119
|
+
# must take a single argument containing a zip entry and return a
|
120
|
+
# compression codec class to be instantiated and used with the entry.
|
121
|
+
# Otherwise, a compression codec class must be specified directly. When
|
122
|
+
# unset, the default compression codec for each entry type is used.
|
123
|
+
# <b>:encryption_codec</b>::
|
124
|
+
# Specifies a proc, lambda, or class. If a proc or lambda is used, it
|
125
|
+
# must take a single argument containing a zip entry and return an
|
126
|
+
# encryption codec class to be instantiated and used with the entry.
|
127
|
+
# Otherwise, an encryption codec class must be specified directly. When
|
128
|
+
# unset, the default encryption codec for each entry type is used.
|
114
129
|
#
|
115
130
|
# Raises Archive::Zip::EntryError if processing the given file path results
|
116
131
|
# in a file not found error.
|
@@ -141,19 +156,37 @@ module Archive; class Zip
|
|
141
156
|
zip_path += '/'
|
142
157
|
end
|
143
158
|
|
159
|
+
# Instantiate the entry.
|
144
160
|
if stat.symlink? then
|
145
161
|
entry = Entry::Symlink.new(zip_path)
|
146
162
|
entry.link_target = ::File.readlink(file_path)
|
147
163
|
elsif stat.file? then
|
148
164
|
entry = Entry::File.new(zip_path)
|
149
165
|
entry.file_path = file_path
|
150
|
-
entry.codec = options[:codec] unless options[:codec].nil?
|
151
166
|
elsif stat.directory? then
|
152
167
|
entry = Entry::Directory.new(zip_path)
|
153
168
|
else
|
154
169
|
raise Zip::EntryError,
|
155
170
|
"unsupported file type `#{stat.ftype}' for file `#{file_path}'"
|
156
171
|
end
|
172
|
+
|
173
|
+
# Set the compression and encryption codecs.
|
174
|
+
unless options[:compression_codec].nil? then
|
175
|
+
if options[:compression_codec].kind_of?(Proc) then
|
176
|
+
entry.compression_codec = options[:compression_codec][entry].new
|
177
|
+
else
|
178
|
+
entry.compression_codec = options[:compression_codec].new
|
179
|
+
end
|
180
|
+
end
|
181
|
+
unless options[:encryption_codec].nil? then
|
182
|
+
if options[:encryption_codec].kind_of?(Proc) then
|
183
|
+
entry.encryption_codec = options[:encryption_codec][entry].new
|
184
|
+
else
|
185
|
+
entry.encryption_codec = options[:encryption_codec].new
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Set the entry's metadata.
|
157
190
|
entry.uid = stat.uid
|
158
191
|
entry.gid = stat.gid
|
159
192
|
entry.mtime = stat.mtime
|
@@ -200,52 +233,54 @@ module Archive; class Zip
|
|
200
233
|
# same.
|
201
234
|
compare_file_records(lfr, cfr)
|
202
235
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
236
|
+
begin
|
237
|
+
# Load the correct compression codec.
|
238
|
+
compression_codec = Codec.create_compression_codec(
|
239
|
+
cfr.compression_method,
|
240
|
+
cfr.general_purpose_flags
|
241
|
+
)
|
242
|
+
rescue Zip::Error => e
|
243
|
+
raise Zip::EntryError, "`#{cfr.zip_path}': #{e.message}"
|
244
|
+
end
|
245
|
+
|
246
|
+
begin
|
247
|
+
# Load the correct encryption codec.
|
248
|
+
encryption_codec = Codec.create_encryption_codec(
|
249
|
+
cfr.general_purpose_flags
|
250
|
+
)
|
251
|
+
rescue Zip::Error => e
|
252
|
+
raise Zip::EntryError, "`#{cfr.zip_path}': #{e.message}"
|
207
253
|
end
|
208
254
|
|
209
|
-
# Load the correct codec.
|
210
|
-
codec = Codec.create(cfr.compression_method, cfr.general_purpose_flags)
|
211
255
|
# Set up a data descriptor with expected values for later comparison.
|
212
|
-
|
256
|
+
expected_data_descriptor = DataDescriptor.new(
|
213
257
|
cfr.crc32,
|
214
258
|
cfr.compressed_size,
|
215
259
|
cfr.uncompressed_size
|
216
260
|
)
|
261
|
+
|
217
262
|
# Create the entry.
|
218
263
|
expanded_path = expand_path(cfr.zip_path)
|
264
|
+
io_window = IOWindow.new(io, io.pos, cfr.compressed_size)
|
219
265
|
if cfr.zip_path[-1..-1] == '/' then
|
220
266
|
# This is a directory entry.
|
221
|
-
|
222
|
-
data_descriptor.verify(DataDescriptor.new(0, 0, 0))
|
223
|
-
rescue => e
|
224
|
-
raise Zip::EntryError, "`#{cfr.zip_path}': #{e.message}"
|
225
|
-
end
|
226
|
-
entry = Entry::Directory.new(expanded_path)
|
267
|
+
entry = Entry::Directory.new(expanded_path, io_window)
|
227
268
|
elsif (cfr.external_file_attributes >> 16) & 0770000 == 0120000 then
|
228
269
|
# This is a symlink entry.
|
229
|
-
entry = Entry::Symlink.new(expanded_path)
|
230
|
-
decompressor = codec.decompressor(
|
231
|
-
IOWindow.new(io, io.pos, cfr.compressed_size)
|
232
|
-
)
|
233
|
-
entry.link_target = decompressor.read
|
234
|
-
begin
|
235
|
-
data_descriptor.verify(decompressor.data_descriptor)
|
236
|
-
rescue => e
|
237
|
-
raise Zip::EntryError, "`#{cfr.zip_path}': #{e.message}"
|
238
|
-
end
|
239
|
-
decompressor.close
|
270
|
+
entry = Entry::Symlink.new(expanded_path, io_window)
|
240
271
|
else
|
241
272
|
# Anything else is a file entry.
|
242
|
-
entry = Entry::File.new(expanded_path)
|
243
|
-
entry.file_data = codec.decompressor(
|
244
|
-
IOWindow.new(io, io.pos, cfr.compressed_size)
|
245
|
-
)
|
246
|
-
entry.expected_data_descriptor = data_descriptor
|
273
|
+
entry = Entry::File.new(expanded_path, io_window)
|
247
274
|
end
|
248
275
|
|
276
|
+
# Set the expected data descriptor so that extraction can be verified.
|
277
|
+
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
|
+
# Record the compression codec.
|
281
|
+
entry.compression_codec = compression_codec
|
282
|
+
# Record the encryption codec.
|
283
|
+
entry.encryption_codec = encryption_codec
|
249
284
|
# Set some entry metadata.
|
250
285
|
entry.mtime = cfr.mtime
|
251
286
|
# Only set mode bits for the entry if the external file attributes are
|
@@ -405,8 +440,11 @@ module Archive; class Zip
|
|
405
440
|
public
|
406
441
|
|
407
442
|
# Creates a new, uninitialized Entry instance using the Store compression
|
408
|
-
# method. The zip path is initialized to _zip_path_.
|
409
|
-
|
443
|
+
# method. The zip path is initialized to _zip_path_. _raw_data_, if
|
444
|
+
# specified, must be a readable, IO-like object containing possibly
|
445
|
+
# compressed/encrypted file data for the entry. It is intended to be used
|
446
|
+
# primarily by the parse class method.
|
447
|
+
def initialize(zip_path, raw_data = nil)
|
410
448
|
self.zip_path = zip_path
|
411
449
|
self.mtime = Time.now
|
412
450
|
self.atime = @mtime
|
@@ -414,7 +452,11 @@ module Archive; class Zip
|
|
414
452
|
self.gid = nil
|
415
453
|
self.mode = 0777
|
416
454
|
self.comment = ''
|
417
|
-
self.
|
455
|
+
self.expected_data_descriptor = nil
|
456
|
+
self.compression_codec = Zip::Codec::Store.new
|
457
|
+
self.encryption_codec = Zip::Codec::NullEncryption.new
|
458
|
+
self.password = nil
|
459
|
+
@raw_data = raw_data
|
418
460
|
@extra_fields = []
|
419
461
|
end
|
420
462
|
|
@@ -428,12 +470,24 @@ module Archive; class Zip
|
|
428
470
|
attr_accessor :uid
|
429
471
|
# The group ID of the owner of this entry.
|
430
472
|
attr_accessor :gid
|
431
|
-
# The
|
473
|
+
# The file mode/permission bits for this entry.
|
432
474
|
attr_accessor :mode
|
433
475
|
# The comment associated with this entry.
|
434
476
|
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.
|
481
|
+
attr_accessor :expected_data_descriptor
|
435
482
|
# The selected compression codec.
|
436
|
-
attr_accessor :
|
483
|
+
attr_accessor :compression_codec
|
484
|
+
# The selected encryption codec.
|
485
|
+
attr_accessor :encryption_codec
|
486
|
+
# The password used with the encryption codec to encrypt or decrypt the file
|
487
|
+
# data for an entry.
|
488
|
+
attr_accessor :password
|
489
|
+
# The raw, possibly compressed and/or encrypted file data for an entry.
|
490
|
+
attr_accessor :raw_data
|
437
491
|
|
438
492
|
# Sets the path in the archive for this entry to _zip_path_ after passing it
|
439
493
|
# through Archive::Zip::Entry.expand_path and ensuring that the result is
|
@@ -467,13 +521,6 @@ module Archive; class Zip
|
|
467
521
|
false
|
468
522
|
end
|
469
523
|
|
470
|
-
# Override this method in descendent classes. It should cause the entry to
|
471
|
-
# be extracted from the archive. This implementation does nothing.
|
472
|
-
# _options_ should be a hash used for specifying extraction options, the
|
473
|
-
# keys of which should not collide with keys used by Archive::Zip#extract.
|
474
|
-
def extract(options = {})
|
475
|
-
end
|
476
|
-
|
477
524
|
# Adds _extra_field_ as an extra field specification to this entry. If
|
478
525
|
# _extra_field_ is an instance of
|
479
526
|
# Archive::Zip::Entry::ExtraField::ExtendedTimestamp, the values of that
|
@@ -507,17 +554,37 @@ module Archive; class Zip
|
|
507
554
|
@local_file_record_position = local_file_record_position
|
508
555
|
bytes_written = 0
|
509
556
|
|
510
|
-
general_purpose_flags
|
511
|
-
|
512
|
-
|
513
|
-
|
557
|
+
general_purpose_flags = compression_codec.general_purpose_flags
|
558
|
+
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.
|
572
|
+
general_purpose_flags |= FLAG_DATA_DESCRIPTOR_FOLLOWS
|
573
|
+
end
|
574
|
+
|
575
|
+
# Select the minimum ZIP specification version needed to extract this
|
576
|
+
# entry.
|
577
|
+
version_needed_to_extract = compression_codec.version_needed_to_extract
|
578
|
+
if encryption_codec.version_needed_to_extract > version_needed_to_extract then
|
579
|
+
version_needed_to_extract = encryption_codec.version_needed_to_extract
|
580
|
+
end
|
514
581
|
|
515
582
|
bytes_written += io.write(LFH_SIGNATURE)
|
516
583
|
bytes_written += io.write(
|
517
584
|
[
|
518
|
-
|
585
|
+
version_needed_to_extract,
|
519
586
|
general_purpose_flags,
|
520
|
-
|
587
|
+
compression_codec.compression_method,
|
521
588
|
mtime.to_dos_time.to_i,
|
522
589
|
0,
|
523
590
|
0,
|
@@ -529,21 +596,52 @@ module Archive; class Zip
|
|
529
596
|
bytes_written += io.write(zip_path)
|
530
597
|
bytes_written += io.write(extra_field_data)
|
531
598
|
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
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
|
+
# Pipeline a compressor into an encryptor, write all the file data to the
|
609
|
+
# compressor, and get a data descriptor from it.
|
610
|
+
encryption_codec.encryptor(io, password) do |e|
|
611
|
+
compression_codec.compressor(e) do |c|
|
612
|
+
dump_file_data(c)
|
613
|
+
c.close(false)
|
614
|
+
@data_descriptor = DataDescriptor.new(
|
615
|
+
c.data_descriptor.crc32,
|
616
|
+
c.data_descriptor.compressed_size + encryption_codec.header_size,
|
617
|
+
c.data_descriptor.uncompressed_size
|
618
|
+
)
|
619
|
+
end
|
620
|
+
e.close(false)
|
538
621
|
end
|
539
622
|
|
540
623
|
bytes_written += @data_descriptor.compressed_size
|
541
624
|
if io.seekable? then
|
625
|
+
# Update the data descriptor located before the compressed data for the
|
626
|
+
# entry.
|
542
627
|
saved_position = io.pos
|
543
628
|
io.pos = @local_file_record_position + 14
|
544
629
|
@data_descriptor.dump(io)
|
545
630
|
io.pos = saved_position
|
546
|
-
|
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.
|
547
645
|
bytes_written += io.write(DD_SIGNATURE)
|
548
646
|
bytes_written += @data_descriptor.dump(io)
|
549
647
|
end
|
@@ -559,18 +657,38 @@ module Archive; class Zip
|
|
559
657
|
def dump_central_file_record(io)
|
560
658
|
bytes_written = 0
|
561
659
|
|
562
|
-
general_purpose_flags
|
563
|
-
|
564
|
-
|
565
|
-
|
660
|
+
general_purpose_flags = compression_codec.general_purpose_flags
|
661
|
+
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.
|
675
|
+
general_purpose_flags |= FLAG_DATA_DESCRIPTOR_FOLLOWS
|
676
|
+
end
|
677
|
+
|
678
|
+
# Select the minimum ZIP specification version needed to extract this
|
679
|
+
# entry.
|
680
|
+
version_needed_to_extract = compression_codec.version_needed_to_extract
|
681
|
+
if encryption_codec.version_needed_to_extract > version_needed_to_extract then
|
682
|
+
version_needed_to_extract = encryption_codec.version_needed_to_extract
|
683
|
+
end
|
566
684
|
|
567
685
|
bytes_written += io.write(CFH_SIGNATURE)
|
568
686
|
bytes_written += io.write(
|
569
687
|
[
|
570
688
|
version_made_by,
|
571
|
-
|
689
|
+
version_needed_to_extract,
|
572
690
|
general_purpose_flags,
|
573
|
-
|
691
|
+
compression_codec.compression_method,
|
574
692
|
mtime.to_dos_time.to_i
|
575
693
|
].pack('vvvvV')
|
576
694
|
)
|
@@ -700,8 +818,8 @@ module Archive; class Zip; module Entry
|
|
700
818
|
|
701
819
|
private
|
702
820
|
|
703
|
-
# Directory entries do not have
|
704
|
-
def
|
821
|
+
# Directory entries do not have file data to write, so do nothing.
|
822
|
+
def dump_file_data(io)
|
705
823
|
end
|
706
824
|
end
|
707
825
|
end; end; end
|
@@ -712,9 +830,6 @@ module Archive; class Zip; module Entry
|
|
712
830
|
class Symlink
|
713
831
|
include Archive::Zip::Entry
|
714
832
|
|
715
|
-
# A string indicating the target of a symlink.
|
716
|
-
attr_accessor :link_target
|
717
|
-
|
718
833
|
# Returns the file type of this entry as the symbol <tt>:symlink</tt>.
|
719
834
|
def ftype
|
720
835
|
:symlink
|
@@ -731,6 +846,37 @@ module Archive; class Zip; module Entry
|
|
731
846
|
super(0120000 | (mode & 07777))
|
732
847
|
end
|
733
848
|
|
849
|
+
# Returns the link target for this entry.
|
850
|
+
#
|
851
|
+
# Raises Archive::Zip::EntryError if decoding the link target from an
|
852
|
+
# archive is required but fails.
|
853
|
+
def link_target
|
854
|
+
return @link_target unless @link_target.nil?
|
855
|
+
|
856
|
+
raw_data.rewind
|
857
|
+
encryption_codec.decryptor(raw_data, password) do |decryptor|
|
858
|
+
compression_codec.decompressor(decryptor) do |decompressor|
|
859
|
+
@link_target = decompressor.read
|
860
|
+
# Verify that the extracted data is good.
|
861
|
+
begin
|
862
|
+
unless expected_data_descriptor.nil? then
|
863
|
+
expected_data_descriptor.verify(decompressor.data_descriptor)
|
864
|
+
end
|
865
|
+
rescue => e
|
866
|
+
raise Zip::EntryError, "`#{zip_path}': #{e.message}"
|
867
|
+
end
|
868
|
+
end
|
869
|
+
end
|
870
|
+
@link_target
|
871
|
+
end
|
872
|
+
|
873
|
+
# Sets the link target for this entry. As a side effect, the raw_data
|
874
|
+
# attribute is set to +nil+.
|
875
|
+
def link_target=(link_target)
|
876
|
+
raw_data = nil
|
877
|
+
@link_target = link_target
|
878
|
+
end
|
879
|
+
|
734
880
|
# Extracts this entry.
|
735
881
|
#
|
736
882
|
# _options_ is a Hash optionally containing the following:
|
@@ -739,17 +885,18 @@ module Archive; class Zip; module Entry
|
|
739
885
|
# the zip path of this entry.
|
740
886
|
# <b>:permissions</b>::
|
741
887
|
# When set to +false+ (the default), POSIX mode/permission bits will be
|
742
|
-
# ignored. Otherwise, they will be restored if possible.
|
888
|
+
# ignored. Otherwise, they will be restored if possible. Not supported
|
889
|
+
# on all platforms.
|
743
890
|
# <b>:ownerships</b>::
|
744
891
|
# When set to +false+ (the default), user and group ownerships will be
|
745
892
|
# ignored. On most systems, only a superuser is able to change
|
746
893
|
# ownerships, so setting this option to +true+ as a regular user may have
|
747
|
-
# no effect.
|
894
|
+
# no effect. Not supported on all platforms.
|
748
895
|
#
|
749
|
-
# Raises Archive::Zip::
|
896
|
+
# Raises Archive::Zip::EntryError if the link_target attribute is not
|
750
897
|
# specified.
|
751
898
|
def extract(options = {})
|
752
|
-
raise Zip::
|
899
|
+
raise Zip::EntryError, 'link_target is nil' if link_target.nil?
|
753
900
|
|
754
901
|
# Ensure that unspecified options have default values.
|
755
902
|
file_path = options.has_key?(:file_path) ?
|
@@ -789,7 +936,7 @@ module Archive; class Zip; module Entry
|
|
789
936
|
private
|
790
937
|
|
791
938
|
# Write the link target to _io_ as the file data for the entry.
|
792
|
-
def
|
939
|
+
def dump_file_data(io)
|
793
940
|
io.write(@link_target)
|
794
941
|
end
|
795
942
|
end
|
@@ -803,40 +950,75 @@ module Archive; class Zip; module Entry
|
|
803
950
|
# Creates a new file entry where _zip_path_ is the path to the entry in the
|
804
951
|
# ZIP archive. The Archive::Zip::Codec::Deflate codec with the default
|
805
952
|
# compression level set (NORMAL) is used by default for compression.
|
806
|
-
|
807
|
-
|
953
|
+
# _raw_data_, if specified, must be a readable, IO-like object containing
|
954
|
+
# possibly compressed/encrypted file data for the entry. It is intended to
|
955
|
+
# be used primarily by the Archive::Zip::Entry.parse class method.
|
956
|
+
def initialize(zip_path, raw_data = nil)
|
957
|
+
super(zip_path, raw_data)
|
808
958
|
@file_path = nil
|
809
959
|
@file_data = nil
|
810
|
-
@
|
811
|
-
@codec = Zip::Codec::Deflate.new
|
960
|
+
@compression_codec = Zip::Codec::Deflate.new
|
812
961
|
end
|
813
962
|
|
814
|
-
#
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
963
|
+
# Returns the file type of this entry as the symbol <tt>:file</tt>.
|
964
|
+
def ftype
|
965
|
+
:file
|
966
|
+
end
|
967
|
+
|
968
|
+
# Returns +true+.
|
969
|
+
def file?
|
970
|
+
true
|
971
|
+
end
|
972
|
+
|
973
|
+
# Overridden in order to ensure that the proper mode bits are set for a
|
974
|
+
# file.
|
975
|
+
def mode=(mode)
|
976
|
+
super(0100000 | (mode & 07777))
|
977
|
+
end
|
819
978
|
|
820
|
-
#
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
979
|
+
# Sets the decryption password.
|
980
|
+
def password=(password)
|
981
|
+
unless @raw_data.nil? then
|
982
|
+
@file_data = nil
|
983
|
+
end
|
984
|
+
@password = password
|
985
|
+
end
|
986
|
+
|
987
|
+
# The path to a file whose contents are to be used for uncompressed file
|
988
|
+
# data. This will be +nil+ if the +file_data+ attribute is set directly.
|
989
|
+
attr_reader :file_path
|
990
|
+
|
991
|
+
# Sets the +file_path+ attribute to _file_path_ which should be a String
|
992
|
+
# usable with File#new to open a file for reading which will provide the
|
993
|
+
# IO-like object for the +file_data+ attribute.
|
994
|
+
def file_path=(file_path)
|
995
|
+
@file_data = nil
|
996
|
+
@raw_data = nil
|
997
|
+
@file_path = file_path
|
998
|
+
end
|
999
|
+
|
1000
|
+
# Returns a readable, IO-like object containing uncompressed file data.
|
825
1001
|
#
|
826
1002
|
# <b>NOTE:</b> It is the responsibility of the user of this attribute to
|
827
1003
|
# ensure that the #close method of the returned IO-like object is called
|
828
1004
|
# when the object is no longer needed.
|
829
1005
|
def file_data
|
830
|
-
|
1006
|
+
return @file_data unless @file_data.nil? || @file_data.closed?
|
1007
|
+
|
1008
|
+
unless raw_data.nil? then
|
1009
|
+
raw_data.rewind
|
1010
|
+
@file_data = compression_codec.decompressor(
|
1011
|
+
encryption_codec.decryptor(raw_data, password)
|
1012
|
+
)
|
1013
|
+
else
|
831
1014
|
if @file_path.nil? then
|
832
|
-
|
1015
|
+
simulated_raw_data = StringIO.new
|
833
1016
|
else
|
834
|
-
|
1017
|
+
simulated_raw_data = ::File.new(@file_path, 'rb')
|
835
1018
|
end
|
836
|
-
# Ensure that the IO-like object can return
|
837
|
-
#
|
838
|
-
|
839
|
-
@file_data = Zip::Codec::Store.new.decompressor(@file_data)
|
1019
|
+
# Ensure that the IO-like object can return a data descriptor so that
|
1020
|
+
# it's possible to verify extraction later if desired.
|
1021
|
+
@file_data = Zip::Codec::Store.new.decompressor(simulated_raw_data)
|
840
1022
|
end
|
841
1023
|
@file_data
|
842
1024
|
end
|
@@ -847,10 +1029,11 @@ module Archive; class Zip; module Entry
|
|
847
1029
|
# then wrapped inside an Archive::Zip::Codec::Store::Unstore instance before
|
848
1030
|
# finally setting the +file_data+ attribute.
|
849
1031
|
#
|
850
|
-
# <b>NOTE:</b> As a side effect, the +file_path+
|
851
|
-
# will be set to +nil+.
|
1032
|
+
# <b>NOTE:</b> As a side effect, the +file_path+ and +raw_data+ attributes
|
1033
|
+
# for this object will be set to +nil+.
|
852
1034
|
def file_data=(file_data)
|
853
1035
|
@file_path = nil
|
1036
|
+
self.raw_data = nil
|
854
1037
|
if file_data.kind_of?(String)
|
855
1038
|
@file_data = StringIO.new(file_data)
|
856
1039
|
else
|
@@ -864,34 +1047,6 @@ module Archive; class Zip; module Entry
|
|
864
1047
|
@file_data
|
865
1048
|
end
|
866
1049
|
|
867
|
-
# The path to a file whose contents are to be used for uncompressed file
|
868
|
-
# data. This will be +nil+ if the +file_data+ attribute is set directly.
|
869
|
-
attr_reader :file_path
|
870
|
-
|
871
|
-
# Sets the +file_path+ attribute to _file_path_ which should be a String
|
872
|
-
# usable with File#new to open a file for reading which will provide the
|
873
|
-
# IO-like object for the +file_data+ attribute.
|
874
|
-
def file_path=(file_path)
|
875
|
-
@file_data = nil
|
876
|
-
@file_path = file_path
|
877
|
-
end
|
878
|
-
|
879
|
-
# Returns the file type of this entry as the symbol <tt>:file</tt>.
|
880
|
-
def ftype
|
881
|
-
:file
|
882
|
-
end
|
883
|
-
|
884
|
-
# Returns +true+.
|
885
|
-
def file?
|
886
|
-
true
|
887
|
-
end
|
888
|
-
|
889
|
-
# Overridden in order to ensure that the proper mode bits are set for a
|
890
|
-
# file.
|
891
|
-
def mode=(mode)
|
892
|
-
super(0100000 | (mode & 07777))
|
893
|
-
end
|
894
|
-
|
895
1050
|
# Extracts this entry.
|
896
1051
|
#
|
897
1052
|
# _options_ is a Hash optionally containing the following:
|
@@ -910,7 +1065,7 @@ module Archive; class Zip; module Entry
|
|
910
1065
|
# When set to +false+ (the default), last accessed and last modified times
|
911
1066
|
# will be ignored. Otherwise, they will be restored if possible.
|
912
1067
|
#
|
913
|
-
# Raises Archive::Zip::
|
1068
|
+
# Raises Archive::Zip::EntryError if the extracted file data appears
|
914
1069
|
# corrupt.
|
915
1070
|
def extract(options = {})
|
916
1071
|
# Ensure that unspecified options have default values.
|
@@ -944,7 +1099,7 @@ module Archive; class Zip; module Entry
|
|
944
1099
|
expected_data_descriptor.verify(file_data.data_descriptor)
|
945
1100
|
end
|
946
1101
|
rescue => e
|
947
|
-
raise Zip::
|
1102
|
+
raise Zip::EntryError, "`#{zip_path}': #{e.message}"
|
948
1103
|
end
|
949
1104
|
|
950
1105
|
# Restore the metadata.
|
@@ -966,7 +1121,7 @@ module Archive; class Zip; module Entry
|
|
966
1121
|
private
|
967
1122
|
|
968
1123
|
# Write the file data to _io_.
|
969
|
-
def
|
1124
|
+
def dump_file_data(io)
|
970
1125
|
while buffer = file_data.read(4096) do io.write(buffer) end
|
971
1126
|
|
972
1127
|
# Attempt to ensure that the file data will still be in a readable state
|