rubyzip 2.4.rc1 → 3.0.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +368 -0
  3. data/README.md +112 -37
  4. data/Rakefile +11 -7
  5. data/lib/zip/central_directory.rb +164 -118
  6. data/lib/zip/compressor.rb +3 -1
  7. data/lib/zip/constants.rb +25 -21
  8. data/lib/zip/crypto/decrypted_io.rb +3 -1
  9. data/lib/zip/crypto/encryption.rb +4 -2
  10. data/lib/zip/crypto/null_encryption.rb +5 -3
  11. data/lib/zip/crypto/traditional_encryption.rb +5 -3
  12. data/lib/zip/decompressor.rb +4 -3
  13. data/lib/zip/deflater.rb +10 -8
  14. data/lib/zip/dirtyable.rb +32 -0
  15. data/lib/zip/dos_time.rb +32 -3
  16. data/lib/zip/entry.rb +263 -199
  17. data/lib/zip/entry_set.rb +9 -7
  18. data/lib/zip/errors.rb +115 -16
  19. data/lib/zip/extra_field/generic.rb +3 -10
  20. data/lib/zip/extra_field/ntfs.rb +4 -2
  21. data/lib/zip/extra_field/old_unix.rb +3 -1
  22. data/lib/zip/extra_field/universal_time.rb +3 -1
  23. data/lib/zip/extra_field/unix.rb +5 -3
  24. data/lib/zip/extra_field/unknown.rb +33 -0
  25. data/lib/zip/extra_field/zip64.rb +12 -5
  26. data/lib/zip/extra_field.rb +15 -21
  27. data/lib/zip/file.rb +143 -264
  28. data/lib/zip/file_split.rb +97 -0
  29. data/lib/zip/filesystem/dir.rb +86 -0
  30. data/lib/zip/filesystem/directory_iterator.rb +48 -0
  31. data/lib/zip/filesystem/file.rb +262 -0
  32. data/lib/zip/filesystem/file_stat.rb +110 -0
  33. data/lib/zip/filesystem/zip_file_name_mapper.rb +81 -0
  34. data/lib/zip/filesystem.rb +26 -595
  35. data/lib/zip/inflater.rb +7 -5
  36. data/lib/zip/input_stream.rb +44 -39
  37. data/lib/zip/ioextras/abstract_input_stream.rb +14 -9
  38. data/lib/zip/ioextras/abstract_output_stream.rb +5 -3
  39. data/lib/zip/ioextras.rb +6 -6
  40. data/lib/zip/null_compressor.rb +3 -1
  41. data/lib/zip/null_decompressor.rb +3 -1
  42. data/lib/zip/null_input_stream.rb +3 -1
  43. data/lib/zip/output_stream.rb +47 -48
  44. data/lib/zip/pass_thru_compressor.rb +3 -1
  45. data/lib/zip/pass_thru_decompressor.rb +4 -2
  46. data/lib/zip/streamable_directory.rb +3 -1
  47. data/lib/zip/streamable_stream.rb +3 -0
  48. data/lib/zip/version.rb +3 -1
  49. data/lib/zip.rb +15 -16
  50. data/rubyzip.gemspec +38 -0
  51. data/samples/example.rb +8 -3
  52. data/samples/example_filesystem.rb +2 -1
  53. data/samples/example_recursive.rb +3 -1
  54. data/samples/gtk_ruby_zip.rb +4 -2
  55. data/samples/qtzip.rb +6 -5
  56. data/samples/write_simple.rb +1 -0
  57. data/samples/zipfind.rb +1 -0
  58. metadata +81 -46
  59. data/TODO +0 -15
  60. data/lib/zip/extra_field/zip64_placeholder.rb +0 -15
data/lib/zip/entry_set.rb CHANGED
@@ -1,7 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zip
2
- class EntrySet #:nodoc:all
4
+ class EntrySet # :nodoc:all
3
5
  include Enumerable
4
- attr_accessor :entry_set, :entry_order
6
+
7
+ attr_reader :entry_set
8
+ protected :entry_set
5
9
 
6
10
  def initialize(an_enumerable = [])
7
11
  super()
@@ -33,10 +37,8 @@ module Zip
33
37
  entry if @entry_set.delete(to_key(entry))
34
38
  end
35
39
 
36
- def each
37
- @entry_set = sorted_entries.dup.each do |_, value|
38
- yield(value)
39
- end
40
+ def each(&block)
41
+ entries.each(&block)
40
42
  end
41
43
 
42
44
  def entries
@@ -70,7 +72,7 @@ module Zip
70
72
  protected
71
73
 
72
74
  def sorted_entries
73
- ::Zip.sort_entries ? Hash[@entry_set.sort] : @entry_set
75
+ ::Zip.sort_entries ? @entry_set.sort.to_h : @entry_set
74
76
  end
75
77
 
76
78
  private
data/lib/zip/errors.rb CHANGED
@@ -1,19 +1,118 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zip
4
+ # The superclass for all rubyzip error types. Simply rescue this one if
5
+ # you don't need to know what sort of error has been raised.
2
6
  class Error < StandardError; end
3
- class EntryExistsError < Error; end
4
- class DestinationFileExistsError < Error; end
5
- class CompressionMethodError < Error; end
6
- class EntryNameError < Error; end
7
- class EntrySizeError < Error; end
8
- class InternalError < Error; end
9
- class GPFBit3Error < Error; end
10
- class DecompressionError < Error; end
11
-
12
- # Backwards compatibility with v1 (delete in v2)
13
- ZipError = Error
14
- ZipEntryExistsError = EntryExistsError
15
- ZipDestinationFileExistsError = DestinationFileExistsError
16
- ZipCompressionMethodError = CompressionMethodError
17
- ZipEntryNameError = EntryNameError
18
- ZipInternalError = InternalError
7
+
8
+ # Error raised if an unsupported compression method is used.
9
+ class CompressionMethodError < Error
10
+ attr_reader :compression_method
11
+
12
+ def initialize(method)
13
+ super()
14
+ @compression_method = method
15
+ end
16
+
17
+ def message
18
+ "Unsupported compression method: #{COMPRESSION_METHODS[@compression_method]}."
19
+ end
20
+ end
21
+
22
+ # Error raised if there is a problem while decompressing an archive entry.
23
+ class DecompressionError < Error
24
+ attr_reader :zlib_error
25
+
26
+ def initialize(zlib_error)
27
+ super()
28
+ @zlib_error = zlib_error
29
+ end
30
+
31
+ def message
32
+ "Zlib error ('#{@zlib_error.message}') while inflating."
33
+ end
34
+ end
35
+
36
+ # Error raised when trying to extract an archive entry over an
37
+ # existing file.
38
+ class DestinationExistsError < Error
39
+ def initialize(destination)
40
+ super()
41
+ @destination = destination
42
+ end
43
+
44
+ def message
45
+ "Cannot create file or directory '#{@destination}'. " \
46
+ 'A file already exists with that name.'
47
+ end
48
+ end
49
+
50
+ # Error raised when trying to add an entry to an archive where the
51
+ # entry name already exists.
52
+ class EntryExistsError < Error
53
+ def initialize(source, name)
54
+ super()
55
+ @source = source
56
+ @name = name
57
+ end
58
+
59
+ def message
60
+ "'#{@source}' failed. Entry #{@name} already exists."
61
+ end
62
+ end
63
+
64
+ # Error raised when an entry name is invalid.
65
+ class EntryNameError < Error
66
+ def initialize(name = nil)
67
+ super()
68
+ @name = name
69
+ end
70
+
71
+ def message
72
+ if @name.nil?
73
+ 'Illegal entry name. Names must have fewer than 65,536 characters.'
74
+ else
75
+ "Illegal entry name '#{@name}'. Names must not start with '/'."
76
+ end
77
+ end
78
+ end
79
+
80
+ # Error raised if an entry is larger on extraction than it is advertised
81
+ # to be.
82
+ class EntrySizeError < Error
83
+ attr_reader :entry
84
+
85
+ def initialize(entry)
86
+ super()
87
+ @entry = entry
88
+ end
89
+
90
+ def message
91
+ "Entry '#{@entry.name}' should be #{@entry.size}B, but is larger when inflated."
92
+ end
93
+ end
94
+
95
+ # Error raised if a split archive is read. Rubyzip does not support reading
96
+ # split archives.
97
+ class SplitArchiveError < Error
98
+ def message
99
+ 'Rubyzip cannot extract from split archives at this time.'
100
+ end
101
+ end
102
+
103
+ # Error raised if there is not enough metadata for the entry to be streamed.
104
+ class StreamingError < Error
105
+ attr_reader :entry
106
+
107
+ def initialize(entry)
108
+ super()
109
+ @entry = entry
110
+ end
111
+
112
+ def message
113
+ "The local header of this entry ('#{@entry.name}') does not contain " \
114
+ 'the correct metadata for `Zip::InputStream` to be able to ' \
115
+ 'uncompress it. Please use `Zip::File` instead of `Zip::InputStream`.'
116
+ end
117
+ end
19
118
  end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zip
2
- class ExtraField::Generic
4
+ class ExtraField::Generic # :nodoc:
3
5
  def self.register_map
4
6
  return unless const_defined?(:HEADER_ID)
5
7
 
@@ -22,15 +24,6 @@ module Zip
22
24
  [binstr[2, 2].unpack1('v'), binstr[4..-1]]
23
25
  end
24
26
 
25
- def ==(other)
26
- return false if self.class != other.class
27
-
28
- each do |k, v|
29
- return false if v != other[k]
30
- end
31
- true
32
- end
33
-
34
27
  def to_local_bin
35
28
  s = pack_for_local
36
29
  self.class.const_get(:HEADER_ID) + [s.bytesize].pack('v') << s
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zip
2
4
  # PKWARE NTFS Extra Field (0x000a)
3
5
  # Only Tag 0x0001 is supported
4
- class ExtraField::NTFS < ExtraField::Generic
6
+ class ExtraField::NTFS < ExtraField::Generic # :nodoc:
5
7
  HEADER_ID = [0x000A].pack('v')
6
8
  register_map
7
9
 
@@ -51,7 +53,7 @@ module Zip
51
53
  # reserved 0 and tag 1
52
54
  s = [0, 1].pack('Vv')
53
55
 
54
- tag1 = ''.force_encoding(Encoding::BINARY)
56
+ tag1 = (+'').force_encoding(Encoding::BINARY)
55
57
  if @mtime
56
58
  tag1 << [to_ntfs_time(@mtime)].pack('Q<')
57
59
  if @atime
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zip
2
4
  # Olf Info-ZIP Extra for UNIX uid/gid and file timestampes
3
- class ExtraField::OldUnix < ExtraField::Generic
5
+ class ExtraField::OldUnix < ExtraField::Generic # :nodoc:
4
6
  HEADER_ID = 'UX'
5
7
  register_map
6
8
 
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zip
2
4
  # Info-ZIP Additional timestamp field
3
- class ExtraField::UniversalTime < ExtraField::Generic
5
+ class ExtraField::UniversalTime < ExtraField::Generic # :nodoc:
4
6
  HEADER_ID = 'UT'
5
7
  register_map
6
8
 
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zip
2
4
  # Info-ZIP Extra for UNIX uid/gid
3
- class ExtraField::IUnix < ExtraField::Generic
5
+ class ExtraField::IUnix < ExtraField::Generic # :nodoc:
4
6
  HEADER_ID = 'Ux'
5
7
  register_map
6
8
 
@@ -20,8 +22,8 @@ module Zip
20
22
  return if !size || size == 0
21
23
 
22
24
  uid, gid = content.unpack('vv')
23
- @uid ||= uid
24
- @gid ||= gid # rubocop:disable Naming/MemoizedInstanceVariableName
25
+ @uid = uid
26
+ @gid = gid
25
27
  end
26
28
 
27
29
  def ==(other)
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zip
4
+ # A class to hold unknown extra fields so that they are preserved.
5
+ class ExtraField::Unknown # :nodoc:
6
+ def initialize
7
+ @local_bin = +''
8
+ @cdir_bin = +''
9
+ end
10
+
11
+ def merge(binstr, local: false)
12
+ return if binstr.empty?
13
+
14
+ if local
15
+ @local_bin << binstr
16
+ else
17
+ @cdir_bin << binstr
18
+ end
19
+ end
20
+
21
+ def to_local_bin
22
+ @local_bin
23
+ end
24
+
25
+ def to_c_dir_bin
26
+ @cdir_bin
27
+ end
28
+
29
+ def ==(other)
30
+ @local_bin == other.to_local_bin && @cdir_bin == other.to_c_dir_bin
31
+ end
32
+ end
33
+ end
@@ -1,7 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zip
2
4
  # Info-ZIP Extra for Zip64 size
3
- class ExtraField::Zip64 < ExtraField::Generic
4
- attr_accessor :original_size, :compressed_size, :relative_header_offset, :disk_start_number
5
+ class ExtraField::Zip64 < ExtraField::Generic # :nodoc:
6
+ attr_accessor :compressed_size, :disk_start_number,
7
+ :original_size, :relative_header_offset
8
+
5
9
  HEADER_ID = ['0100'].pack('H*')
6
10
  register_map
7
11
 
@@ -36,7 +40,9 @@ module Zip
36
40
  def parse(original_size, compressed_size, relative_header_offset = nil, disk_start_number = nil)
37
41
  @original_size = extract(8, 'Q<') if original_size == 0xFFFFFFFF
38
42
  @compressed_size = extract(8, 'Q<') if compressed_size == 0xFFFFFFFF
39
- @relative_header_offset = extract(8, 'Q<') if relative_header_offset && relative_header_offset == 0xFFFFFFFF
43
+ if relative_header_offset && relative_header_offset == 0xFFFFFFFF
44
+ @relative_header_offset = extract(8, 'Q<')
45
+ end
40
46
  @disk_start_number = extract(4, 'V') if disk_start_number && disk_start_number == 0xFFFF
41
47
  @content = nil
42
48
  [@original_size || original_size,
@@ -51,7 +57,8 @@ module Zip
51
57
  private :extract
52
58
 
53
59
  def pack_for_local
54
- # local header entries must contain original size and compressed size; other fields do not apply
60
+ # Local header entries must contain original size and compressed size;
61
+ # other fields do not apply.
55
62
  return '' unless @original_size && @compressed_size
56
63
 
57
64
  [@original_size, @compressed_size].pack('Q<Q<')
@@ -59,7 +66,7 @@ module Zip
59
66
 
60
67
  def pack_for_c_dir
61
68
  # central directory entries contain only fields that didn't fit in the main entry part
62
- packed = ''.b
69
+ packed = (+'').force_encoding('BINARY')
63
70
  packed << [@original_size].pack('Q<') if @original_size
64
71
  packed << [@compressed_size].pack('Q<') if @compressed_size
65
72
  packed << [@relative_header_offset].pack('Q<') if @relative_header_offset
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zip
2
- class ExtraField < Hash
4
+ class ExtraField < Hash # :nodoc:all
3
5
  ID_MAP = {}
4
6
 
5
- def initialize(binstr = nil)
6
- merge(binstr) if binstr
7
+ def initialize(binstr = nil, local: false)
8
+ merge(binstr, local: local) if binstr
7
9
  end
8
10
 
9
11
  def extra_field_type_exist(binstr, id, len, index)
@@ -16,25 +18,18 @@ module Zip
16
18
  end
17
19
  end
18
20
 
19
- def extra_field_type_unknown(binstr, len, index)
20
- create_unknown_item unless self['Unknown']
21
+ def extra_field_type_unknown(binstr, len, index, local)
22
+ self['Unknown'] ||= Unknown.new
23
+
21
24
  if !len || len + 4 > binstr[index..-1].bytesize
22
- self['Unknown'] << binstr[index..-1]
25
+ self['Unknown'].merge(binstr[index..-1], local: local)
23
26
  return
24
27
  end
25
- self['Unknown'] << binstr[index, len + 4]
26
- end
27
28
 
28
- def create_unknown_item
29
- s = +''
30
- class << s
31
- alias_method :to_c_dir_bin, :to_s
32
- alias_method :to_local_bin, :to_s
33
- end
34
- self['Unknown'] = s
29
+ self['Unknown'].merge(binstr[index, len + 4], local: local)
35
30
  end
36
31
 
37
- def merge(binstr)
32
+ def merge(binstr, local: false)
38
33
  return if binstr.empty?
39
34
 
40
35
  i = 0
@@ -44,8 +39,7 @@ module Zip
44
39
  if id && ID_MAP.member?(id)
45
40
  extra_field_type_exist(binstr, id, len, i)
46
41
  elsif id
47
- create_unknown_item unless self['Unknown']
48
- break unless extra_field_type_unknown(binstr, len, i)
42
+ break unless extra_field_type_unknown(binstr, len, i, local)
49
43
  end
50
44
  i += len + 4
51
45
  end
@@ -59,8 +53,8 @@ module Zip
59
53
  self[name] = field_class.new
60
54
  end
61
55
 
62
- # place Unknown last, so "extra" data that is missing the proper signature/size
63
- # does not prevent known fields from being read back in
56
+ # Place Unknown last, so "extra" data that is missing the proper
57
+ # signature/size does not prevent known fields from being read back in.
64
58
  def ordered_values
65
59
  result = []
66
60
  each { |k, v| k == 'Unknown' ? result.push(v) : result.unshift(v) }
@@ -90,12 +84,12 @@ module Zip
90
84
  end
91
85
  end
92
86
 
87
+ require 'zip/extra_field/unknown'
93
88
  require 'zip/extra_field/generic'
94
89
  require 'zip/extra_field/universal_time'
95
90
  require 'zip/extra_field/old_unix'
96
91
  require 'zip/extra_field/unix'
97
92
  require 'zip/extra_field/zip64'
98
- require 'zip/extra_field/zip64_placeholder'
99
93
  require 'zip/extra_field/ntfs'
100
94
 
101
95
  # Copyright (C) 2002, 2003 Thomas Sondergaard