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 CHANGED
@@ -15,12 +15,13 @@ lib/archive/support/zlib.rb
15
15
  lib/archive/zip.rb
16
16
  lib/archive/zip/codec.rb
17
17
  lib/archive/zip/codec/deflate.rb
18
+ lib/archive/zip/codec/null_encryption.rb
18
19
  lib/archive/zip/codec/store.rb
20
+ lib/archive/zip/codec/traditional_encryption.rb
21
+ lib/archive/zip/data_descriptor.rb
19
22
  lib/archive/zip/entry.rb
20
- lib/archive/zip/datadescriptor.rb
21
- lib/archive/zip/extrafield.rb
22
- lib/archive/zip/extrafield/extendedtimestamp.rb
23
- lib/archive/zip/extrafield/raw.rb
24
- lib/archive/zip/extrafield/unix.rb
25
23
  lib/archive/zip/error.rb
26
- test/test_archive.rb
24
+ lib/archive/zip/extra_field.rb
25
+ lib/archive/zip/extra_field/extended_timestamp.rb
26
+ lib/archive/zip/extra_field/raw.rb
27
+ lib/archive/zip/extra_field/unix.rb
data/NEWS CHANGED
@@ -6,6 +6,16 @@ detailed information is available in the rest of the documentation.
6
6
  <b>NOTE:</b> Date stamps in the following entries are in YYYY/MM/DD format.
7
7
 
8
8
 
9
+ == v0.2.0 (2008/08/06)
10
+
11
+ * Traditional (weak) encryption is now supported.
12
+ * Adding new encryption methods should be easier now.
13
+ * Fixed a bug where the compression codec for an entry loaded from an archive
14
+ was not being recorded.
15
+ * The _compression_codec_ attribute for Entry instances is now used instead of
16
+ the _codec_ attribute to access the compression codec of the entry.
17
+
18
+
9
19
  == v0.1.1 (2008/07/11)
10
20
 
11
21
  * Archive files are now closed when the Archive::Zip object is closed even when
@@ -15,15 +25,15 @@ detailed information is available in the rest of the documentation.
15
25
 
16
26
  == v0.1.0 (2008/07/10)
17
27
 
18
- * Initial release
19
- * Archive creation and extraction is supported with only a few lines of code
28
+ * Initial release.
29
+ * Archive creation and extraction is supported with only a few lines of code.
20
30
  (See README)
21
- * Archives can be updated "in place" or dumped out to other files or pipes
22
- * Files, symlinks, and directories are supported within archives
23
- * Unix permission/mode bits are supported
31
+ * Archives can be updated "in place" or dumped out to other files or pipes.
32
+ * Files, symlinks, and directories are supported within archives.
33
+ * Unix permission/mode bits are supported.
24
34
  * Unix user and group ownerships are supported.
25
35
  * Unix last accessed and last modified times are supported.
26
36
  * Entry extension (AKA extra field) implementations can be added on the fly.
27
37
  * Unknown entry extension types are preserved during archive processing.
28
- * Deflate and Store compression codecs supported out of the box
29
- * More compression codecs can be added on the fly
38
+ * Deflate and Store compression codecs are supported out of the box.
39
+ * More compression codecs can be added on the fly.
data/README CHANGED
@@ -56,6 +56,23 @@ Create a few archives:
56
56
  :path_prefix => 'a/b/c'
57
57
  )
58
58
 
59
+ # Add the contents of a_directory to example5.zip and encrypt Ruby source
60
+ # files.
61
+ require 'archive/zip/codec/null_encryption'
62
+ require 'archive/zip/codec/traditional_encryption'
63
+ Archive::Zip.archive(
64
+ 'example5.zip',
65
+ 'a_directory/.',
66
+ :encryption_codec => lambda do |entry|
67
+ if entry.file? and entry.zip_path =~ /\.rb$/ then
68
+ Archive::Zip::Codec::TraditionalEncryption
69
+ else
70
+ Archive::Zip::Codec::NullEncryption
71
+ end
72
+ end,
73
+ :password => 'seakrit'
74
+ )
75
+
59
76
  # Create a new archive which will be written to a pipe.
60
77
  # Assume $stdout is the write end a pipe.
61
78
  # (ruby example.rb | cat >example.zip)
@@ -93,6 +110,9 @@ Now extract those archives:
93
110
  :overwrite => :older
94
111
  )
95
112
 
113
+ # Extract example5.zip to a_destination, decrypting the contents.
114
+ Archive::Zip.extract('example5.zip', 'a_destination', :password => 'seakrit')
115
+
96
116
 
97
117
  == Features
98
118
 
@@ -107,6 +127,7 @@ Now extract those archives:
107
127
  9. Unknown entry extension types are preserved during archive processing.
108
128
  10. The Deflate and Store compression codecs are supported out of the box.
109
129
  11. More compression codecs can be added on the fly.
130
+ 12. Traditional (weak) encryption is supported out of the box.
110
131
 
111
132
 
112
133
  == Known Bugs/Limitations
@@ -119,7 +140,7 @@ Now extract those archives:
119
140
  4. Reading archives from non-seekable IO, such as pipes and sockets, is not
120
141
  supported.
121
142
  5. MSDOS permission attributes are not supported.
122
- 6. Encryption is not supported.
143
+ 6. Strong encryption is not supported.
123
144
  7. Zip64 is not supported.
124
145
  8. Digital signatures are not supported.
125
146
 
data/lib/archive/zip.rb CHANGED
@@ -19,12 +19,12 @@ module Archive # :nodoc:
19
19
  # entries, directory entries, file entries, and symlink entries. File and
20
20
  # directory accessed and modified times, POSIX permissions, and ownerships can
21
21
  # be archived and restored as well depending on platform support for such
22
- # metadata.
22
+ # metadata. Traditional (weak) encryption is also supported.
23
23
  #
24
- # Zip64, digital signatures, and encryption are not supported. ZIP archives
25
- # can only be read from seekable kinds of IO, such as files; reading archives
26
- # from pipes or any other non-seekable kind of IO is not supported. However,
27
- # writing to such IO objects <b><em>IS</em></b> supported.
24
+ # Zip64, digital signatures, and strong encryption are not supported. ZIP
25
+ # archives can only be read from seekable kinds of IO, such as files; reading
26
+ # archives from pipes or any other non-seekable kind of IO is not supported.
27
+ # However, writing to such IO objects <b><em>IS</em></b> supported.
28
28
  class Zip
29
29
  include Enumerable
30
30
 
@@ -251,15 +251,24 @@ module Archive # :nodoc:
251
251
  # from the archive and +false+ if it should be included. <b>NOTE:</b> If
252
252
  # a directory is excluded in this way, the <b>:recursion</b> option has no
253
253
  # effect for it.
254
- # <b>:ignore_error</b>::
255
- # When set to +false+ (the default), an error generated while creating an
256
- # archive entry for a file will be raised. Otherwise, the bad file is
257
- # skipped.
254
+ # <b>:password</b>::
255
+ # Specifies a proc, lambda, or a String. If a proc or lambda is used, it
256
+ # must take a single argument containing a zip entry and return a String
257
+ # to be used as an encryption key for the entry. If a String is used, it
258
+ # will be used as an encryption key for all encrypted entries.
259
+ # <b>:on_error</b>::
260
+ # Specifies a proc or lambda which is called when an exception is raised
261
+ # during the archival of an entry. It takes two arguments, a file path
262
+ # and an exception object generated while attempting to archive the entry.
263
+ # If <tt>:retry</tt> is returned, archival of the entry is attempted
264
+ # again. If <tt>:skip</tt> is returned, the entry is skipped. Otherwise,
265
+ # the exception is raised.
258
266
  # Any other options which are supported by Archive::Zip::Entry.from_file are
259
267
  # also supported.
260
268
  #
261
269
  # Raises Archive::Zip::IOError if called after #close. Raises
262
- # Archive::Zip::EntryError if the <b>:ignore_error</b> option is +false+ and
270
+ # Archive::Zip::EntryError if the <b>:on_error</b> option is either unset or
271
+ # indicates that the error should be raised and
263
272
  # Archive::Zip::Entry.from_file raises an error.
264
273
  #
265
274
  # == Example
@@ -333,7 +342,6 @@ module Archive # :nodoc:
333
342
  options[:directories] = true unless options.has_key?(:directories)
334
343
  options[:symlinks] = false unless options.has_key?(:symlinks)
335
344
  options[:flatten] = false unless options.has_key?(:flatten)
336
- options[:ignore_error] = false unless options.has_key?(:ignore_error)
337
345
 
338
346
  # Flattening the directory structure implies that directories are skipped
339
347
  # and that the path prefix should be ignored.
@@ -364,10 +372,16 @@ module Archive # :nodoc:
364
372
  ! options[:symlinks]
365
373
  )
366
374
  )
367
- rescue Zip::EntryError
368
- # Ignore the error if requested.
369
- if options[:ignore_error] then
370
- next
375
+ rescue StandardError => error
376
+ unless options[:on_error].nil? then
377
+ case options[:on_error][path, error]
378
+ when :retry
379
+ retry
380
+ when :skip
381
+ next
382
+ else
383
+ raise
384
+ end
371
385
  else
372
386
  raise
373
387
  end
@@ -375,16 +389,22 @@ module Archive # :nodoc:
375
389
 
376
390
  # Skip this entry if so directed.
377
391
  if (zip_entry.symlink? && ! options[:symlinks]) ||
378
- (! options[:exclude].nil? && options[:exclude].call(zip_entry)) then
392
+ (! options[:exclude].nil? && options[:exclude][zip_entry]) then
379
393
  next
380
394
  end
381
395
 
396
+ # Set the encryption key for the entry.
397
+ if options[:password].kind_of?(String) then
398
+ zip_entry.password = options[:password]
399
+ elsif ! options[:password].nil? then
400
+ zip_entry.password = options[:password][zip_entry]
401
+ end
402
+
382
403
  # Add entries for directories (if requested) and files/symlinks.
383
404
  if (! zip_entry.directory? || options[:directories]) then
384
405
  add_entry(zip_entry)
385
406
  end
386
407
 
387
-
388
408
  # Recurse into subdirectories (if requested).
389
409
  if zip_entry.directory? && options[:recursion] then
390
410
  archive(
@@ -436,6 +456,18 @@ module Archive # :nodoc:
436
456
  # Specifies a proc or lambda which takes a single argument containing a
437
457
  # zip entry and returns +true+ if the entry should be skipped during
438
458
  # extraction and +false+ if it should be extracted.
459
+ # <b>:password</b>::
460
+ # Specifies a proc, lambda, or a String. If a proc or lambda is used, it
461
+ # must take a single argument containing a zip entry and return a String
462
+ # to be used as a decryption key for the entry. If a String is used, it
463
+ # will be used as a decryption key for all encrypted entries.
464
+ # <b>:on_error</b>::
465
+ # Specifies a proc or lambda which is called when an exception is raised
466
+ # during the extraction of an entry. It takes two arguments, a zip entry
467
+ # and an exception object generated while attempting to extract the entry.
468
+ # If <tt>:retry</tt> is returned, extraction of the entry is attempted
469
+ # again. If <tt>:skip</tt> is returned, the entry is skipped. Otherwise,
470
+ # the exception is raised.
439
471
  # Any other options which are supported by Archive::Zip::Entry#extract are
440
472
  # also supported.
441
473
  #
@@ -522,23 +554,44 @@ module Archive # :nodoc:
522
554
  file_exists = File.exist?(file_path)
523
555
  file_mtime = File.mtime(file_path) if file_exists
524
556
 
525
- # Skip this entry if so directed.
526
- if (! file_exists && ! options[:create]) ||
527
- (file_exists &&
528
- (options[:overwrite] == :never ||
529
- options[:overwrite] == :older && entry.mtime <= file_mtime)) ||
530
- (! options[:exclude].nil? && options[:exclude].call(entry)) then
531
- next
532
- end
557
+ begin
558
+ # Skip this entry if so directed.
559
+ if (! file_exists && ! options[:create]) ||
560
+ (file_exists &&
561
+ (options[:overwrite] == :never ||
562
+ options[:overwrite] == :older && entry.mtime <= file_mtime)) ||
563
+ (! options[:exclude].nil? && options[:exclude][entry]) then
564
+ next
565
+ end
533
566
 
534
- if entry.directory? then
535
- # Record the directories as they are encountered.
536
- directories << entry
537
- elsif entry.file? || (entry.symlink? && options[:symlinks]) then
538
- # Extract files and symlinks.
539
- entry.extract(
540
- options.merge(:file_path => file_path)
541
- )
567
+ # Set the decryption key for the entry.
568
+ if options[:password].kind_of?(String) then
569
+ entry.password = options[:password]
570
+ elsif ! options[:password].nil? then
571
+ entry.password = options[:password][entry]
572
+ end
573
+
574
+ if entry.directory? then
575
+ # Record the directories as they are encountered.
576
+ directories << entry
577
+ elsif entry.file? || (entry.symlink? && options[:symlinks]) then
578
+ # Extract files and symlinks.
579
+ entry.extract(
580
+ options.merge(:file_path => file_path)
581
+ )
582
+ end
583
+ rescue StandardError => error
584
+ unless options[:on_error].nil? then
585
+ case options[:on_error][entry, error]
586
+ when :retry
587
+ retry
588
+ when :skip
589
+ else
590
+ raise
591
+ end
592
+ else
593
+ raise
594
+ end
542
595
  end
543
596
  end
544
597
 
@@ -546,9 +599,25 @@ module Archive # :nodoc:
546
599
  # Then extract the directory entries in depth first order so that time
547
600
  # stamps, ownerships, and permissions can be properly restored.
548
601
  directories.sort { |a, b| b.zip_path <=> a.zip_path }.each do |entry|
549
- entry.extract(
550
- options.merge(:file_path => File.join(destination, entry.zip_path))
551
- )
602
+ begin
603
+ entry.extract(
604
+ options.merge(
605
+ :file_path => File.join(destination, entry.zip_path)
606
+ )
607
+ )
608
+ rescue StandardError => error
609
+ unless options[:on_error].nil? then
610
+ case options[:on_error][entry, error]
611
+ when :retry
612
+ retry
613
+ when :skip
614
+ else
615
+ raise
616
+ end
617
+ else
618
+ raise
619
+ end
620
+ end
552
621
  end
553
622
  end
554
623
 
@@ -7,24 +7,45 @@ module Archive; class Zip
7
7
  # of Archive::Zip::Codec::Deflate and Archive::Zip::Codec::Store for details
8
8
  # on implementing custom codecs.
9
9
  module Codec
10
- # A Hash mapping compression methods to codec implementations. New codecs
11
- # must add a mapping here when defined in order to be used.
12
- CODECS = {}
10
+ # A Hash mapping compression methods to compression codec implementations.
11
+ # New compression codecs must add a mapping here when defined in order to be
12
+ # used.
13
+ COMPRESSION_CODECS = {}
13
14
 
14
- # Returns a new codec instance based on _compression_method_ and
15
+ # A Hash mapping encryption methods to encryption codec implementations.
16
+ # New encryption codecs must add a mapping here when defined in order to be
17
+ # used.
18
+ ENCRYPTION_CODECS = {}
19
+
20
+ # Returns a new compression codec instance based on _compression_method_ and
15
21
  # _general_purpose_flags_.
16
- def self.create(compression_method, general_purpose_flags)
17
- CODECS[compression_method].new(general_purpose_flags)
22
+ def self.create_compression_codec(compression_method, general_purpose_flags)
23
+ # Load the standard compression codecs.
24
+ require 'archive/zip/codec/deflate'
25
+ require 'archive/zip/codec/store'
26
+
27
+ codec = COMPRESSION_CODECS[compression_method].new(general_purpose_flags)
28
+ raise Zip::Error, 'unsupported compression codec' if codec.nil?
29
+ codec
18
30
  end
19
31
 
20
- # Returns +true+ if _compression_method_ is mapped to a codec, +false+
21
- # otherwise.
22
- def self.supported?(compression_method)
23
- CODECS.has_key?(compression_method)
32
+ # Returns a new encryption codec instance based on _general_purpose_flags_.
33
+ #
34
+ # <b>NOTE:</b> The signature of this method will have to change in order to
35
+ # support the strong encryption codecs. This is intended to be an internal
36
+ # method anyway, so this fact should not cause major issues for users of
37
+ # this library.
38
+ def self.create_encryption_codec(general_purpose_flags)
39
+ general_purpose_flags &= 0b0000000001000001
40
+ if general_purpose_flags == 0b0000000000000000 then
41
+ require 'archive/zip/codec/null_encryption'
42
+ codec = NullEncryption.new
43
+ elsif general_purpose_flags == 0b0000000000000001 then
44
+ require 'archive/zip/codec/traditional_encryption'
45
+ codec = TraditionalEncryption.new
46
+ end
47
+ raise Zip::Error, 'unsupported encryption codec' if codec.nil?
48
+ codec
24
49
  end
25
50
  end
26
51
  end; end
27
-
28
- # Load the standard codecs.
29
- require 'archive/zip/codec/deflate'
30
- require 'archive/zip/codec/store'
@@ -1,21 +1,21 @@
1
1
  require 'archive/support/zlib'
2
2
  require 'archive/zip/codec'
3
- require 'archive/zip/datadescriptor'
3
+ require 'archive/zip/data_descriptor'
4
4
 
5
5
  module Archive; class Zip; module Codec
6
- # Archive::Zip::Codec::Deflate is a handle for the deflate-inflate codec as
7
- # defined in Zlib which provides convenient interfaces for writing and reading
8
- # deflated streams.
6
+ # Archive::Zip::Codec::Deflate is a handle for the deflate-inflate codec
7
+ # as defined in Zlib which provides convenient interfaces for writing and
8
+ # reading deflated streams.
9
9
  class Deflate
10
- # Archive::Zip::Codec::Deflate::Deflate extends Zlib::ZWriter in order to
10
+ # Archive::Zip::Codec::Deflate::Compress extends Zlib::ZWriter in order to
11
11
  # specify the standard Zlib options required by ZIP archives and to provide
12
- # a close method which can optionally close the delegate IO-like object. In
13
- # addition a convenience method is provided for generating DataDescriptor
12
+ # a close method which can optionally close the delegate IO-like object.
13
+ # In addition a convenience method is provided for generating DataDescriptor
14
14
  # objects based on the data which is passed through this object.
15
15
  #
16
16
  # Instances of this class should only be accessed via the
17
17
  # Archive::Zip::Codec::Deflate#compressor method.
18
- class Deflate < Zlib::ZWriter
18
+ class Compress < Zlib::ZWriter
19
19
  # Creates a new instance of this class with the given arguments using #new
20
20
  # and then passes the instance to the given block. The #close method is
21
21
  # guaranteed to be called after the block completes.
@@ -61,15 +61,15 @@ module Archive; class Zip; module Codec
61
61
  end
62
62
  end
63
63
 
64
- # Archive::Zip::Codec::Deflate::Inflate extends Zlib::ZReader in order to
64
+ # Archive::Zip::Codec::Deflate::Decompress extends Zlib::ZReader in order to
65
65
  # specify the standard Zlib options required by ZIP archives and to provide
66
- # a close method which can optionally close the delegate IO-like object. In
67
- # addition a convenience method is provided for generating DataDescriptor
66
+ # a close method which can optionally close the delegate IO-like object.
67
+ # In addition a convenience method is provided for generating DataDescriptor
68
68
  # objects based on the data which is passed through this object.
69
69
  #
70
70
  # Instances of this class should only be accessed via the
71
71
  # Archive::Zip::Codec::Deflate#decompressor method.
72
- class Inflate < Zlib::ZReader
72
+ class Decompress < Zlib::ZReader
73
73
  # Creates a new instance of this class with the given arguments using #new
74
74
  # and then passes the instance to the given block. The #close method is
75
75
  # guaranteed to be called after the block completes.
@@ -112,11 +112,12 @@ module Archive; class Zip; module Codec
112
112
  end
113
113
  end
114
114
 
115
- # The numeric identifier assigned to this codec by the ZIP specification.
115
+ # The numeric identifier assigned to this compression codec by the ZIP
116
+ # specification.
116
117
  ID = 8
117
118
 
118
- # Register this codec.
119
- CODECS[ID] = self
119
+ # Register this compression codec.
120
+ COMPRESSION_CODECS[ID] = self
120
121
 
121
122
  # A bit mask used to denote that Zlib's default compression level should be
122
123
  # used.
@@ -131,7 +132,7 @@ module Archive; class Zip; module Codec
131
132
  SUPER_FAST = 0b110
132
133
 
133
134
  # This method signature is part of the interface contract expected by
134
- # Archive::Zip::Entry for codec objects.
135
+ # Archive::Zip::Entry for compression codec objects.
135
136
  #
136
137
  # Creates a new instance of this class using bits 1 and 2 of
137
138
  # _general_purpose_flags_ to select a compression level to be used by
@@ -155,45 +156,46 @@ module Archive; class Zip; module Codec
155
156
  end
156
157
 
157
158
  # This method signature is part of the interface contract expected by
158
- # Archive::Zip::Entry for codec objects.
159
+ # Archive::Zip::Entry for compression codec objects.
159
160
  #
160
- # A convenience method for creating an Archive::Zip::Codec::Deflate::Deflate
161
- # object using that class' open method. The compression level for the open
162
- # method is pulled from the value of the _general_purpose_flags_ argument of
163
- # new.
161
+ # A convenience method for creating an
162
+ # Archive::Zip::Codec::Deflate::Compress object using that class' open
163
+ # method. The compression level for the open method is pulled from the
164
+ # value of the _general_purpose_flags_ argument of new.
164
165
  def compressor(io, &b)
165
- Deflate.open(io, @zlib_compression_level, &b)
166
+ Compress.open(io, @zlib_compression_level, &b)
166
167
  end
167
168
 
168
169
  # This method signature is part of the interface contract expected by
169
- # Archive::Zip::Entry for codec objects.
170
+ # Archive::Zip::Entry for compression codec objects.
170
171
  #
171
- # A convenience method for creating an Archive::Zip::Codec::Deflate::Inflate
172
- # object using that class' open method.
172
+ # A convenience method for creating an
173
+ # Archive::Zip::Codec::Deflate::Decompress object using that class' open
174
+ # method.
173
175
  def decompressor(io, &b)
174
- Inflate.open(io, &b)
176
+ Decompress.open(io, &b)
175
177
  end
176
178
 
177
179
  # This method signature is part of the interface contract expected by
178
- # Archive::Zip::Entry for codec objects.
180
+ # Archive::Zip::Entry for compression codec objects.
179
181
  #
180
182
  # Returns an integer which indicates the version of the official ZIP
181
- # specification which introduced support for this codec.
183
+ # specification which introduced support for this compression codec.
182
184
  def version_needed_to_extract
183
185
  0x0014
184
186
  end
185
187
 
186
188
  # This method signature is part of the interface contract expected by
187
- # Archive::Zip::Entry for codec objects.
189
+ # Archive::Zip::Entry for compression codec objects.
188
190
  #
189
- # Returns an integer used to flag that this codec is used for a particular
190
- # ZIP archive entry.
191
+ # Returns an integer used to flag that this compression codec is used for a
192
+ # particular ZIP archive entry.
191
193
  def compression_method
192
194
  ID
193
195
  end
194
196
 
195
197
  # This method signature is part of the interface contract expected by
196
- # Archive::Zip::Entry for codec objects.
198
+ # Archive::Zip::Entry for compression codec objects.
197
199
  #
198
200
  # Returns an integer representing the general purpose flags of a ZIP archive
199
201
  # entry where bits 1 and 2 are set according to the compression level