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.
@@ -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 like-named attributes of
25
- # _other_ and raises Archive::Zip::Error for any mismatches.
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}"
@@ -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/extrafield'
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
- # _dump_compressed_data_. _extract_ should be a public method with the
16
- # following signature:
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
- # _dump_compressed_data_ should be a private method with the following
30
- # signature:
32
+ # _dump_file_data_ should be a private method with the following signature:
31
33
  #
32
- # def dump_compressed_data(io)
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>:codec</b>::
112
- # When unset, the default codec for file entries is used; otherwise, a
113
- # file entry which is created will use the codec set with this option.
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
- # Raise an error if the codec is not supported.
204
- unless Codec.supported?(cfr.compression_method) then
205
- raise Zip::EntryError,
206
- "`#{cfr.zip_path}': unsupported compression method"
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
- data_descriptor = DataDescriptor.new(
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
- begin
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
- def initialize(zip_path)
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.codec = Zip::Codec::Store.new
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 the file mode/permission bits for this entry.
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 :codec
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 = codec.general_purpose_flags
511
- # Flag that the data descriptor record will follow the compressed file
512
- # data of this entry unless the IO object can be access randomly.
513
- general_purpose_flags |= 0b1000 unless io.seekable?
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
- codec.version_needed_to_extract,
585
+ version_needed_to_extract,
519
586
  general_purpose_flags,
520
- codec.compression_method,
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
- # Get a compressor, write all the file data to it, and get a data
533
- # descriptor from it.
534
- codec.compressor(io) do |c|
535
- dump_compressed_data(c)
536
- c.close(false)
537
- @data_descriptor = c.data_descriptor
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
- else
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 = codec.general_purpose_flags
563
- # Flag that the data descriptor record will follow the compressed file
564
- # data of this entry unless the IO object can be access randomly.
565
- general_purpose_flags |= FLAG_DATA_DESCRIPTOR_FOLLOWS unless io.seekable?
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
- codec.version_needed_to_extract,
689
+ version_needed_to_extract,
572
690
  general_purpose_flags,
573
- codec.compression_method,
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 compressed data to write, so do nothing.
704
- def dump_compressed_data(io)
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::ExtractError if the link_target attribute is not
896
+ # Raises Archive::Zip::EntryError if the link_target attribute is not
750
897
  # specified.
751
898
  def extract(options = {})
752
- raise Zip::ExtractError, 'link_target is nil' if link_target.nil?
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 dump_compressed_data(io)
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
- def initialize(zip_path)
807
- super(zip_path)
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
- @expected_data_descriptor = nil
811
- @codec = Zip::Codec::Deflate.new
960
+ @compression_codec = Zip::Codec::Deflate.new
812
961
  end
813
962
 
814
- # An Archive::Zip::Entry::DataDescriptor instance which should contain the
815
- # expected CRC32 checksum, compressed size, and uncompressed size for the
816
- # file data. When not +nil+, this is used by #extract to confirm that the
817
- # data extraction was successful.
818
- attr_accessor :expected_data_descriptor
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
- # Returns a readable, IO-like object containing uncompressed file data. If
821
- # the file data has not been explicitly set previously, this will return a
822
- # Archive::Zip::Codec::Store::Unstore instance wrapping either a File
823
- # instance based on the +file_path+ attribute, if set, or an empty StringIO
824
- # instance otherwise.
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
- if @file_data.nil? || @file_data.closed? then
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
- @file_data = StringIO.new
1015
+ simulated_raw_data = StringIO.new
833
1016
  else
834
- @file_data = ::File.new(@file_path, 'rb')
1017
+ simulated_raw_data = ::File.new(@file_path, 'rb')
835
1018
  end
836
- # Ensure that the IO-like object can return CRC32 and data size
837
- # information so that it's possible to verify extraction later if
838
- # desired.
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+ attribute for this object
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::ExtractError if the extracted file data appears
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::ExtractError, "`#{zip_path}': #{e.message}"
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 dump_compressed_data(io)
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