archive-zip 0.1.1 → 0.2.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/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
|