rubyzip 2.4.1 → 3.2.1

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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +476 -0
  3. data/LICENSE.md +24 -0
  4. data/README.md +180 -40
  5. data/Rakefile +15 -13
  6. data/lib/zip/central_directory.rb +172 -124
  7. data/lib/zip/compressor.rb +3 -1
  8. data/lib/zip/constants.rb +29 -21
  9. data/lib/zip/crypto/aes_encryption.rb +120 -0
  10. data/lib/zip/crypto/decrypted_io.rb +20 -14
  11. data/lib/zip/crypto/encryption.rb +4 -2
  12. data/lib/zip/crypto/null_encryption.rb +5 -13
  13. data/lib/zip/crypto/traditional_encryption.rb +10 -6
  14. data/lib/zip/decompressor.rb +4 -3
  15. data/lib/zip/deflater.rb +12 -8
  16. data/lib/zip/dirtyable.rb +32 -0
  17. data/lib/zip/dos_time.rb +45 -5
  18. data/lib/zip/entry.rb +391 -264
  19. data/lib/zip/entry_set.rb +11 -9
  20. data/lib/zip/errors.rb +136 -16
  21. data/lib/zip/extra_field/aes.rb +50 -0
  22. data/lib/zip/extra_field/generic.rb +10 -11
  23. data/lib/zip/extra_field/ntfs.rb +6 -4
  24. data/lib/zip/extra_field/old_unix.rb +3 -1
  25. data/lib/zip/extra_field/universal_time.rb +3 -1
  26. data/lib/zip/extra_field/unix.rb +5 -3
  27. data/lib/zip/extra_field/unknown.rb +35 -0
  28. data/lib/zip/extra_field/zip64.rb +19 -5
  29. data/lib/zip/extra_field.rb +25 -23
  30. data/lib/zip/file.rb +174 -267
  31. data/lib/zip/file_split.rb +91 -0
  32. data/lib/zip/filesystem/dir.rb +86 -0
  33. data/lib/zip/filesystem/directory_iterator.rb +48 -0
  34. data/lib/zip/filesystem/file.rb +263 -0
  35. data/lib/zip/filesystem/file_stat.rb +110 -0
  36. data/lib/zip/filesystem/zip_file_name_mapper.rb +81 -0
  37. data/lib/zip/filesystem.rb +27 -596
  38. data/lib/zip/inflater.rb +11 -8
  39. data/lib/zip/input_stream.rb +76 -57
  40. data/lib/zip/ioextras/abstract_input_stream.rb +19 -13
  41. data/lib/zip/ioextras/abstract_output_stream.rb +13 -3
  42. data/lib/zip/ioextras.rb +7 -7
  43. data/lib/zip/null_compressor.rb +3 -1
  44. data/lib/zip/null_decompressor.rb +6 -3
  45. data/lib/zip/null_input_stream.rb +3 -1
  46. data/lib/zip/output_stream.rb +60 -57
  47. data/lib/zip/pass_thru_compressor.rb +3 -1
  48. data/lib/zip/pass_thru_decompressor.rb +8 -5
  49. data/lib/zip/streamable_directory.rb +3 -1
  50. data/lib/zip/streamable_stream.rb +4 -1
  51. data/lib/zip/version.rb +4 -1
  52. data/lib/zip.rb +25 -22
  53. data/rubyzip.gemspec +39 -0
  54. data/samples/example.rb +8 -3
  55. data/samples/example_filesystem.rb +3 -2
  56. data/samples/example_recursive.rb +3 -1
  57. data/samples/gtk_ruby_zip.rb +5 -3
  58. data/samples/qtzip.rb +7 -6
  59. data/samples/write_simple.rb +2 -1
  60. data/samples/zipfind.rb +1 -0
  61. metadata +86 -52
  62. data/TODO +0 -15
  63. 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
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zip
4
+ # Info-ZIP Extra for AES encryption
5
+ class ExtraField::AES < ExtraField::Generic # :nodoc:
6
+ attr_reader :vendor_version, :vendor_id, :encryption_strength, :compression_method
7
+
8
+ HEADER_ID = [0x9901].pack('v')
9
+ register_map
10
+
11
+ def initialize(binstr = nil)
12
+ @vendor_version = nil
13
+ @vendor_id = nil
14
+ @encryption_strength = nil
15
+ @compression_method = nil
16
+ binstr && merge(binstr)
17
+ end
18
+
19
+ def ==(other)
20
+ @vendor_version == other.vendor_version &&
21
+ @vendor_id == other.vendor_id &&
22
+ @encryption_strength == other.encryption_strength &&
23
+ @compression_method == other.compression_method
24
+ end
25
+
26
+ def merge(binstr)
27
+ return if binstr.empty?
28
+
29
+ size, content = initial_parse(binstr)
30
+ (size && content) || return
31
+
32
+ @vendor_version, @vendor_id,
33
+ @encryption_strength, @compression_method = content.unpack('va2Cv')
34
+ end
35
+
36
+ # We can never suppress the AES extra field as it is needed to read the file.
37
+ def suppress?
38
+ false
39
+ end
40
+
41
+ def pack_for_local
42
+ [@vendor_version, @vendor_id,
43
+ @encryption_strength, @compression_method].pack('va2Cv')
44
+ end
45
+
46
+ def pack_for_c_dir
47
+ pack_for_local
48
+ end
49
+ end
50
+ 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
 
@@ -7,7 +9,7 @@ module Zip
7
9
  end
8
10
 
9
11
  def self.name
10
- @name ||= to_s.split('::')[-1]
12
+ @name ||= to_s.split('::').last.downcase.to_sym
11
13
  end
12
14
 
13
15
  # return field [size, content] or false
@@ -19,26 +21,23 @@ module Zip
19
21
  return false
20
22
  end
21
23
 
22
- [binstr[2, 2].unpack1('v'), binstr[4..-1]]
24
+ [binstr[2, 2].unpack1('v'), binstr[4..]]
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
27
+ # Default strategy is to suppress all extra fields if we're asked to.
28
+ # Specific extra field types can override this if they need to be kept.
29
+ def suppress?
31
30
  true
32
31
  end
33
32
 
34
33
  def to_local_bin
35
34
  s = pack_for_local
36
- self.class.const_get(:HEADER_ID) + [s.bytesize].pack('v') << s
35
+ (self.class.const_get(:HEADER_ID) + [s.bytesize].pack('v')) << s
37
36
  end
38
37
 
39
38
  def to_c_dir_bin
40
39
  s = pack_for_c_dir
41
- self.class.const_get(:HEADER_ID) + [s.bytesize].pack('v') << s
40
+ (self.class.const_get(:HEADER_ID) + [s.bytesize].pack('v')) << s
42
41
  end
43
42
  end
44
43
  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,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'generic'
4
+
5
+ module Zip
6
+ # A class to hold unknown extra fields so that they are preserved.
7
+ class ExtraField::Unknown < ExtraField::Generic # :nodoc:
8
+ def initialize
9
+ @local_bin = +''
10
+ @cdir_bin = +''
11
+ end
12
+
13
+ def merge(binstr, local: false)
14
+ return if binstr.empty?
15
+
16
+ if local
17
+ @local_bin << binstr
18
+ else
19
+ @cdir_bin << binstr
20
+ end
21
+ end
22
+
23
+ def to_local_bin
24
+ @local_bin
25
+ end
26
+
27
+ def to_c_dir_bin
28
+ @cdir_bin
29
+ end
30
+
31
+ def ==(other)
32
+ @local_bin == other.to_local_bin && @cdir_bin == other.to_c_dir_bin
33
+ end
34
+ end
35
+ 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,
@@ -50,8 +56,16 @@ module Zip
50
56
  end
51
57
  private :extract
52
58
 
59
+ # We can suppress the zip64 extra field unless we know the size is large or
60
+ # the relative header offset is large (for central directory entries).
61
+ def suppress?
62
+ !(@original_size && @original_size >= 0xFFFFFFFF) ||
63
+ (@relative_header_offset && @relative_header_offset >= 0xFFFFFFFF)
64
+ end
65
+
53
66
  def pack_for_local
54
- # local header entries must contain original size and compressed size; other fields do not apply
67
+ # Local header entries must contain original size and compressed size;
68
+ # other fields do not apply.
55
69
  return '' unless @original_size && @compressed_size
56
70
 
57
71
  [@original_size, @compressed_size].pack('Q<Q<')
@@ -59,7 +73,7 @@ module Zip
59
73
 
60
74
  def pack_for_c_dir
61
75
  # central directory entries contain only fields that didn't fit in the main entry part
62
- packed = ''.b
76
+ packed = (+'').force_encoding('BINARY')
63
77
  packed << [@original_size].pack('Q<') if @original_size
64
78
  packed << [@compressed_size].pack('Q<') if @compressed_size
65
79
  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,14 +53,21 @@ 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
- each { |k, v| k == 'Unknown' ? result.push(v) : result.unshift(v) }
60
+ each { |k, v| k == :unknown ? result.push(v) : result.unshift(v) }
67
61
  result
68
62
  end
69
63
 
64
+ # Remove any extra fields that indicate they can be safely suppressed.
65
+ def suppress_fields!(fields)
66
+ reject! do |k, v|
67
+ v.suppress? if fields == true || [*fields].include?(k)
68
+ end
69
+ end
70
+
70
71
  def to_local_bin
71
72
  ordered_values.map! { |v| v.to_local_bin.force_encoding('BINARY') }.join
72
73
  end
@@ -90,13 +91,14 @@ module Zip
90
91
  end
91
92
  end
92
93
 
94
+ require 'zip/extra_field/unknown'
93
95
  require 'zip/extra_field/generic'
94
96
  require 'zip/extra_field/universal_time'
95
97
  require 'zip/extra_field/old_unix'
96
98
  require 'zip/extra_field/unix'
97
99
  require 'zip/extra_field/zip64'
98
- require 'zip/extra_field/zip64_placeholder'
99
100
  require 'zip/extra_field/ntfs'
101
+ require 'zip/extra_field/aes'
100
102
 
101
103
  # Copyright (C) 2002, 2003 Thomas Sondergaard
102
104
  # rubyzip is free software; you can redistribute it and/or