rubyzip 2.4.1 → 3.0.0.rc1

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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +419 -0
  3. data/LICENSE.md +24 -0
  4. data/README.md +137 -37
  5. data/Rakefile +11 -7
  6. data/lib/zip/central_directory.rb +169 -123
  7. data/lib/zip/compressor.rb +3 -1
  8. data/lib/zip/constants.rb +29 -21
  9. data/lib/zip/crypto/decrypted_io.rb +4 -2
  10. data/lib/zip/crypto/encryption.rb +4 -2
  11. data/lib/zip/crypto/null_encryption.rb +6 -4
  12. data/lib/zip/crypto/traditional_encryption.rb +8 -6
  13. data/lib/zip/decompressor.rb +4 -3
  14. data/lib/zip/deflater.rb +10 -8
  15. data/lib/zip/dirtyable.rb +32 -0
  16. data/lib/zip/dos_time.rb +43 -4
  17. data/lib/zip/entry.rb +333 -242
  18. data/lib/zip/entry_set.rb +11 -9
  19. data/lib/zip/errors.rb +136 -16
  20. data/lib/zip/extra_field/generic.rb +6 -13
  21. data/lib/zip/extra_field/ntfs.rb +6 -4
  22. data/lib/zip/extra_field/old_unix.rb +3 -1
  23. data/lib/zip/extra_field/universal_time.rb +3 -1
  24. data/lib/zip/extra_field/unix.rb +5 -3
  25. data/lib/zip/extra_field/unknown.rb +33 -0
  26. data/lib/zip/extra_field/zip64.rb +12 -5
  27. data/lib/zip/extra_field.rb +16 -22
  28. data/lib/zip/file.rb +166 -264
  29. data/lib/zip/file_split.rb +91 -0
  30. data/lib/zip/filesystem/dir.rb +86 -0
  31. data/lib/zip/filesystem/directory_iterator.rb +48 -0
  32. data/lib/zip/filesystem/file.rb +262 -0
  33. data/lib/zip/filesystem/file_stat.rb +110 -0
  34. data/lib/zip/filesystem/zip_file_name_mapper.rb +81 -0
  35. data/lib/zip/filesystem.rb +27 -596
  36. data/lib/zip/inflater.rb +7 -5
  37. data/lib/zip/input_stream.rb +50 -50
  38. data/lib/zip/ioextras/abstract_input_stream.rb +16 -11
  39. data/lib/zip/ioextras/abstract_output_stream.rb +5 -3
  40. data/lib/zip/ioextras.rb +7 -7
  41. data/lib/zip/null_compressor.rb +3 -1
  42. data/lib/zip/null_decompressor.rb +3 -1
  43. data/lib/zip/null_input_stream.rb +3 -1
  44. data/lib/zip/output_stream.rb +55 -56
  45. data/lib/zip/pass_thru_compressor.rb +3 -1
  46. data/lib/zip/pass_thru_decompressor.rb +4 -2
  47. data/lib/zip/streamable_directory.rb +3 -1
  48. data/lib/zip/streamable_stream.rb +3 -0
  49. data/lib/zip/version.rb +3 -1
  50. data/lib/zip.rb +18 -22
  51. data/rubyzip.gemspec +39 -0
  52. data/samples/example.rb +8 -3
  53. data/samples/example_filesystem.rb +3 -2
  54. data/samples/example_recursive.rb +3 -1
  55. data/samples/gtk_ruby_zip.rb +4 -2
  56. data/samples/qtzip.rb +6 -5
  57. data/samples/write_simple.rb +2 -1
  58. data/samples/zipfind.rb +1 -0
  59. metadata +87 -51
  60. data/TODO +0 -15
  61. 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
@@ -59,18 +61,18 @@ module Zip
59
61
  end
60
62
 
61
63
  def glob(pattern, flags = ::File::FNM_PATHNAME | ::File::FNM_DOTMATCH | ::File::FNM_EXTGLOB)
62
- entries.map do |entry|
64
+ entries.filter_map do |entry|
63
65
  next nil unless ::File.fnmatch(pattern, entry.name.chomp('/'), flags)
64
66
 
65
67
  yield(entry) if block_given?
66
68
  entry
67
- end.compact
69
+ end
68
70
  end
69
71
 
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,139 @@
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
+ # The compression method that has caused this error.
11
+ attr_reader :compression_method
12
+
13
+ # Create a new CompressionMethodError with the specified incorrect
14
+ # compression method.
15
+ def initialize(method)
16
+ super()
17
+ @compression_method = method
18
+ end
19
+
20
+ # The message returned by this error.
21
+ def message
22
+ "Unsupported compression method: #{COMPRESSION_METHODS[@compression_method]}."
23
+ end
24
+ end
25
+
26
+ # Error raised if there is a problem while decompressing an archive entry.
27
+ class DecompressionError < Error
28
+ # The error from the underlying Zlib library that caused this error.
29
+ attr_reader :zlib_error
30
+
31
+ # Create a new DecompressionError with the specified underlying Zlib
32
+ # error.
33
+ def initialize(zlib_error)
34
+ super()
35
+ @zlib_error = zlib_error
36
+ end
37
+
38
+ # The message returned by this error.
39
+ def message
40
+ "Zlib error ('#{@zlib_error.message}') while inflating."
41
+ end
42
+ end
43
+
44
+ # Error raised when trying to extract an archive entry over an
45
+ # existing file.
46
+ class DestinationExistsError < Error
47
+ # Create a new DestinationExistsError with the clashing destination.
48
+ def initialize(destination)
49
+ super()
50
+ @destination = destination
51
+ end
52
+
53
+ # The message returned by this error.
54
+ def message
55
+ "Cannot create file or directory '#{@destination}'. " \
56
+ 'A file already exists with that name.'
57
+ end
58
+ end
59
+
60
+ # Error raised when trying to add an entry to an archive where the
61
+ # entry name already exists.
62
+ class EntryExistsError < Error
63
+ # Create a new EntryExistsError with the specified source and name.
64
+ def initialize(source, name)
65
+ super()
66
+ @source = source
67
+ @name = name
68
+ end
69
+
70
+ # The message returned by this error.
71
+ def message
72
+ "'#{@source}' failed. Entry #{@name} already exists."
73
+ end
74
+ end
75
+
76
+ # Error raised when an entry name is invalid.
77
+ class EntryNameError < Error
78
+ # Create a new EntryNameError with the specified name.
79
+ def initialize(name = nil)
80
+ super()
81
+ @name = name
82
+ end
83
+
84
+ # The message returned by this error.
85
+ def message
86
+ if @name.nil?
87
+ 'Illegal entry name. Names must have fewer than 65,536 characters.'
88
+ else
89
+ "Illegal entry name '#{@name}'. Names must not start with '/'."
90
+ end
91
+ end
92
+ end
93
+
94
+ # Error raised if an entry is larger on extraction than it is advertised
95
+ # to be.
96
+ class EntrySizeError < Error
97
+ # The entry that has caused this error.
98
+ attr_reader :entry
99
+
100
+ # Create a new EntrySizeError with the specified entry.
101
+ def initialize(entry)
102
+ super()
103
+ @entry = entry
104
+ end
105
+
106
+ # The message returned by this error.
107
+ def message
108
+ "Entry '#{@entry.name}' should be #{@entry.size}B, but is larger when inflated."
109
+ end
110
+ end
111
+
112
+ # Error raised if a split archive is read. Rubyzip does not support reading
113
+ # split archives.
114
+ class SplitArchiveError < Error
115
+ # The message returned by this error.
116
+ def message
117
+ 'Rubyzip cannot extract from split archives at this time.'
118
+ end
119
+ end
120
+
121
+ # Error raised if there is not enough metadata for the entry to be streamed.
122
+ class StreamingError < Error
123
+ # The entry that has caused this error.
124
+ attr_reader :entry
125
+
126
+ # Create a new StreamingError with the specified entry.
127
+ def initialize(entry)
128
+ super()
129
+ @entry = entry
130
+ end
131
+
132
+ # The message returned by this error.
133
+ def message
134
+ "The local header of this entry ('#{@entry.name}') does not contain " \
135
+ 'the correct metadata for `Zip::InputStream` to be able to ' \
136
+ 'uncompress it. Please use `Zip::File` instead of `Zip::InputStream`.'
137
+ end
138
+ end
19
139
  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
 
@@ -19,26 +21,17 @@ module Zip
19
21
  return false
20
22
  end
21
23
 
22
- [binstr[2, 2].unpack1('v'), binstr[4..-1]]
23
- end
24
-
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
24
+ [binstr[2, 2].unpack1('v'), binstr[4..]]
32
25
  end
33
26
 
34
27
  def to_local_bin
35
28
  s = pack_for_local
36
- self.class.const_get(:HEADER_ID) + [s.bytesize].pack('v') << s
29
+ (self.class.const_get(:HEADER_ID) + [s.bytesize].pack('v')) << s
37
30
  end
38
31
 
39
32
  def to_c_dir_bin
40
33
  s = pack_for_c_dir
41
- self.class.const_get(:HEADER_ID) + [s.bytesize].pack('v') << s
34
+ (self.class.const_get(:HEADER_ID) + [s.bytesize].pack('v')) << s
42
35
  end
43
36
  end
44
37
  end
@@ -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
 
@@ -23,7 +25,7 @@ module Zip
23
25
  size, content = initial_parse(binstr)
24
26
  (size && content) || return
25
27
 
26
- content = content[4..-1]
28
+ content = content[4..]
27
29
  tags = parse_tags(content)
28
30
 
29
31
  tag1 = tags[1]
@@ -51,7 +53,7 @@ module Zip
51
53
  # reserved 0 and tag 1
52
54
  s = [0, 1].pack('Vv')
53
55
 
54
- tag1 = ''.b
56
+ tag1 = (+'').force_encoding(Encoding::BINARY)
55
57
  if @mtime
56
58
  tag1 << [to_ntfs_time(@mtime)].pack('Q<')
57
59
  if @atime
@@ -84,7 +86,7 @@ module Zip
84
86
  end
85
87
 
86
88
  def from_ntfs_time(ntfs_time)
87
- ::Zip::DOSTime.at(ntfs_time / WINDOWS_TICK - SEC_TO_UNIX_EPOCH)
89
+ ::Zip::DOSTime.at((ntfs_time / WINDOWS_TICK) - SEC_TO_UNIX_EPOCH)
88
90
  end
89
91
 
90
92
  def to_ntfs_time(time)
@@ -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
- if !len || len + 4 > binstr[index..-1].bytesize
22
- self['Unknown'] << binstr[index..-1]
21
+ def extra_field_type_unknown(binstr, len, index, local)
22
+ self['Unknown'] ||= Unknown.new
23
+
24
+ if !len || len + 4 > binstr[index..].bytesize
25
+ self['Unknown'].merge(binstr[index..], 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