rubyzip 1.3.0 → 3.4.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.
- checksums.yaml +4 -4
- data/Changelog.md +516 -0
- data/LICENSE.md +24 -0
- data/README.md +213 -51
- data/Rakefile +17 -12
- data/lib/rubyzip.rb +3 -0
- data/lib/zip/central_directory.rb +196 -123
- data/lib/zip/compressor.rb +3 -1
- data/lib/zip/constants.rb +81 -21
- data/lib/zip/crypto/aes_encryption.rb +120 -0
- data/lib/zip/crypto/decrypted_io.rb +46 -0
- data/lib/zip/crypto/encryption.rb +4 -2
- data/lib/zip/crypto/null_encryption.rb +5 -13
- data/lib/zip/crypto/traditional_encryption.rb +22 -16
- data/lib/zip/decompressor.rb +21 -2
- data/lib/zip/deflater.rb +12 -8
- data/lib/zip/dirtyable.rb +32 -0
- data/lib/zip/dos_time.rb +65 -13
- data/lib/zip/entry.rb +433 -249
- data/lib/zip/entry_set.rb +13 -9
- data/lib/zip/errors.rb +152 -15
- data/lib/zip/extra_field/aes.rb +50 -0
- data/lib/zip/extra_field/generic.rb +18 -18
- data/lib/zip/extra_field/ntfs.rb +10 -4
- data/lib/zip/extra_field/old_unix.rb +6 -2
- data/lib/zip/extra_field/universal_time.rb +45 -13
- data/lib/zip/extra_field/unix.rb +7 -3
- data/lib/zip/extra_field/unknown.rb +35 -0
- data/lib/zip/extra_field/zip64.rb +23 -7
- data/lib/zip/extra_field.rb +31 -27
- data/lib/zip/file.rb +225 -241
- data/lib/zip/file_split.rb +91 -0
- data/lib/zip/filesystem/dir.rb +86 -0
- data/lib/zip/filesystem/directory_iterator.rb +48 -0
- data/lib/zip/filesystem/file.rb +263 -0
- data/lib/zip/filesystem/file_stat.rb +110 -0
- data/lib/zip/filesystem/zip_file_name_mapper.rb +81 -0
- data/lib/zip/filesystem.rb +32 -585
- data/lib/zip/inflater.rb +28 -37
- data/lib/zip/input_stream.rb +113 -54
- data/lib/zip/ioextras/abstract_input_stream.rb +154 -55
- data/lib/zip/ioextras/abstract_output_stream.rb +13 -3
- data/lib/zip/ioextras.rb +8 -8
- data/lib/zip/null_compressor.rb +3 -1
- data/lib/zip/null_decompressor.rb +7 -12
- data/lib/zip/null_input_stream.rb +3 -1
- data/lib/zip/output_stream.rb +69 -50
- data/lib/zip/pass_thru_compressor.rb +5 -3
- data/lib/zip/pass_thru_decompressor.rb +17 -23
- data/lib/zip/streamable_directory.rb +6 -4
- data/lib/zip/streamable_stream.rb +10 -11
- data/lib/zip/version.rb +4 -1
- data/lib/zip.rb +32 -7
- data/rubyzip.gemspec +39 -0
- data/samples/example.rb +9 -4
- data/samples/example_filesystem.rb +4 -3
- data/samples/example_recursive.rb +3 -1
- data/samples/gtk_ruby_zip.rb +23 -21
- data/samples/qtzip.rb +13 -12
- data/samples/write_simple.rb +3 -4
- data/samples/zipfind.rb +24 -22
- metadata +85 -177
- data/TODO +0 -15
- data/lib/zip/extra_field/zip64_placeholder.rb +0 -15
- data/test/basic_zip_file_test.rb +0 -60
- data/test/case_sensitivity_test.rb +0 -69
- data/test/central_directory_entry_test.rb +0 -69
- data/test/central_directory_test.rb +0 -100
- data/test/crypto/null_encryption_test.rb +0 -57
- data/test/crypto/traditional_encryption_test.rb +0 -80
- data/test/data/WarnInvalidDate.zip +0 -0
- data/test/data/file1.txt +0 -46
- data/test/data/file1.txt.deflatedData +0 -0
- data/test/data/file2.txt +0 -1504
- data/test/data/globTest/foo/bar/baz/foo.txt +0 -0
- data/test/data/globTest/foo.txt +0 -0
- data/test/data/globTest/food.txt +0 -0
- data/test/data/globTest.zip +0 -0
- data/test/data/gpbit3stored.zip +0 -0
- data/test/data/mimetype +0 -1
- data/test/data/notzippedruby.rb +0 -7
- data/test/data/ntfs.zip +0 -0
- data/test/data/oddExtraField.zip +0 -0
- data/test/data/path_traversal/Makefile +0 -10
- data/test/data/path_traversal/jwilk/README.md +0 -5
- data/test/data/path_traversal/jwilk/absolute1.zip +0 -0
- data/test/data/path_traversal/jwilk/absolute2.zip +0 -0
- data/test/data/path_traversal/jwilk/dirsymlink.zip +0 -0
- data/test/data/path_traversal/jwilk/dirsymlink2a.zip +0 -0
- data/test/data/path_traversal/jwilk/dirsymlink2b.zip +0 -0
- data/test/data/path_traversal/jwilk/relative0.zip +0 -0
- data/test/data/path_traversal/jwilk/relative2.zip +0 -0
- data/test/data/path_traversal/jwilk/symlink.zip +0 -0
- data/test/data/path_traversal/relative1.zip +0 -0
- data/test/data/path_traversal/tilde.zip +0 -0
- data/test/data/path_traversal/tuzovakaoff/README.md +0 -3
- data/test/data/path_traversal/tuzovakaoff/absolutepath.zip +0 -0
- data/test/data/path_traversal/tuzovakaoff/symlink.zip +0 -0
- data/test/data/rubycode.zip +0 -0
- data/test/data/rubycode2.zip +0 -0
- data/test/data/test.xls +0 -0
- data/test/data/testDirectory.bin +0 -0
- data/test/data/zip64-sample.zip +0 -0
- data/test/data/zipWithDirs.zip +0 -0
- data/test/data/zipWithEncryption.zip +0 -0
- data/test/deflater_test.rb +0 -65
- data/test/encryption_test.rb +0 -42
- data/test/entry_set_test.rb +0 -163
- data/test/entry_test.rb +0 -154
- data/test/errors_test.rb +0 -35
- data/test/extra_field_test.rb +0 -76
- data/test/file_extract_directory_test.rb +0 -54
- data/test/file_extract_test.rb +0 -145
- data/test/file_permissions_test.rb +0 -65
- data/test/file_split_test.rb +0 -57
- data/test/file_test.rb +0 -666
- data/test/filesystem/dir_iterator_test.rb +0 -58
- data/test/filesystem/directory_test.rb +0 -139
- data/test/filesystem/file_mutating_test.rb +0 -87
- data/test/filesystem/file_nonmutating_test.rb +0 -508
- data/test/filesystem/file_stat_test.rb +0 -64
- data/test/gentestfiles.rb +0 -126
- data/test/inflater_test.rb +0 -14
- data/test/input_stream_test.rb +0 -182
- data/test/ioextras/abstract_input_stream_test.rb +0 -102
- data/test/ioextras/abstract_output_stream_test.rb +0 -106
- data/test/ioextras/fake_io_test.rb +0 -18
- data/test/local_entry_test.rb +0 -154
- data/test/output_stream_test.rb +0 -128
- data/test/pass_thru_compressor_test.rb +0 -30
- data/test/pass_thru_decompressor_test.rb +0 -14
- data/test/path_traversal_test.rb +0 -141
- data/test/samples/example_recursive_test.rb +0 -37
- data/test/settings_test.rb +0 -95
- data/test/test_helper.rb +0 -234
- data/test/unicode_file_names_and_comments_test.rb +0 -62
- data/test/zip64_full_test.rb +0 -51
- data/test/zip64_support_test.rb +0 -14
data/lib/zip/entry_set.rb
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Zip
|
|
2
|
-
class EntrySet
|
|
4
|
+
class EntrySet # :nodoc:all
|
|
3
5
|
include Enumerable
|
|
4
|
-
|
|
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
|
-
|
|
38
|
-
yield(value)
|
|
39
|
-
end
|
|
40
|
+
def each(&block)
|
|
41
|
+
entries.each(&block)
|
|
40
42
|
end
|
|
41
43
|
|
|
42
44
|
def entries
|
|
@@ -50,6 +52,7 @@ module Zip
|
|
|
50
52
|
|
|
51
53
|
def ==(other)
|
|
52
54
|
return false unless other.kind_of?(EntrySet)
|
|
55
|
+
|
|
53
56
|
@entry_set.values == other.entry_set.values
|
|
54
57
|
end
|
|
55
58
|
|
|
@@ -58,17 +61,18 @@ module Zip
|
|
|
58
61
|
end
|
|
59
62
|
|
|
60
63
|
def glob(pattern, flags = ::File::FNM_PATHNAME | ::File::FNM_DOTMATCH | ::File::FNM_EXTGLOB)
|
|
61
|
-
entries.
|
|
64
|
+
entries.filter_map do |entry|
|
|
62
65
|
next nil unless ::File.fnmatch(pattern, entry.name.chomp('/'), flags)
|
|
66
|
+
|
|
63
67
|
yield(entry) if block_given?
|
|
64
68
|
entry
|
|
65
|
-
end
|
|
69
|
+
end
|
|
66
70
|
end
|
|
67
71
|
|
|
68
72
|
protected
|
|
69
73
|
|
|
70
74
|
def sorted_entries
|
|
71
|
-
::Zip.sort_entries ?
|
|
75
|
+
::Zip.sort_entries ? @entry_set.sort.to_h : @entry_set
|
|
72
76
|
end
|
|
73
77
|
|
|
74
78
|
private
|
data/lib/zip/errors.rb
CHANGED
|
@@ -1,18 +1,155 @@
|
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
class CompressionMethodError < Error
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
+
class EntryNumberMismatchError < Error
|
|
113
|
+
# Create a new EntryNumberMismatchError with the specified size and size in bytes.
|
|
114
|
+
def initialize(size, size_in_bytes)
|
|
115
|
+
super()
|
|
116
|
+
@size = size
|
|
117
|
+
@size_in_bytes = size_in_bytes
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# The message returned by this error.
|
|
121
|
+
def message
|
|
122
|
+
"Zip consistency problem: an impossibly high number of entries (#{@size}) " \
|
|
123
|
+
'is declared in this file, compared to the size of the central directory ' \
|
|
124
|
+
"(#{@size_in_bytes} bytes)."
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Error raised if a split archive is read. Rubyzip does not support reading
|
|
129
|
+
# split archives.
|
|
130
|
+
class SplitArchiveError < Error
|
|
131
|
+
# The message returned by this error.
|
|
132
|
+
def message
|
|
133
|
+
'Rubyzip cannot extract from split archives at this time.'
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Error raised if there is not enough metadata for the entry to be streamed.
|
|
138
|
+
class StreamingError < Error
|
|
139
|
+
# The entry that has caused this error.
|
|
140
|
+
attr_reader :entry
|
|
141
|
+
|
|
142
|
+
# Create a new StreamingError with the specified entry.
|
|
143
|
+
def initialize(entry)
|
|
144
|
+
super()
|
|
145
|
+
@entry = entry
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# The message returned by this error.
|
|
149
|
+
def message
|
|
150
|
+
"The local header of this entry ('#{@entry.name}') does not contain " \
|
|
151
|
+
'the correct metadata for `Zip::InputStream` to be able to ' \
|
|
152
|
+
'uncompress it. Please use `Zip::File` instead of `Zip::InputStream`.'
|
|
153
|
+
end
|
|
154
|
+
end
|
|
18
155
|
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,43 +1,43 @@
|
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
return unless const_defined?(:HEADER_ID)
|
|
7
|
+
|
|
8
|
+
::Zip::ExtraField::ID_MAP[const_get(:HEADER_ID)] = self
|
|
7
9
|
end
|
|
8
10
|
|
|
9
11
|
def self.name
|
|
10
|
-
@name ||= to_s.split('::')
|
|
12
|
+
@name ||= to_s.split('::').last.downcase.to_sym
|
|
11
13
|
end
|
|
12
14
|
|
|
13
15
|
# return field [size, content] or false
|
|
14
16
|
def initial_parse(binstr)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
$stderr.puts 'Warning: weired extra feild header ID. skip parsing'
|
|
17
|
+
return false unless binstr
|
|
18
|
+
|
|
19
|
+
if binstr[0, 2] != self.class.const_get(:HEADER_ID)
|
|
20
|
+
warn 'WARNING: weird extra field header ID. Skip parsing it.'
|
|
20
21
|
return false
|
|
21
22
|
end
|
|
22
|
-
|
|
23
|
+
|
|
24
|
+
[binstr[2, 2].unpack1('v'), binstr[4..]]
|
|
23
25
|
end
|
|
24
26
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return false if v != other[k]
|
|
29
|
-
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?
|
|
30
30
|
true
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def to_local_bin
|
|
34
34
|
s = pack_for_local
|
|
35
|
-
self.class.const_get(:HEADER_ID) + [s.bytesize].pack('v') << s
|
|
35
|
+
(self.class.const_get(:HEADER_ID) + [s.bytesize].pack('v')) << s
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def to_c_dir_bin
|
|
39
39
|
s = pack_for_c_dir
|
|
40
|
-
self.class.const_get(:HEADER_ID) + [s.bytesize].pack('v') << s
|
|
40
|
+
(self.class.const_get(:HEADER_ID) + [s.bytesize].pack('v')) << s
|
|
41
41
|
end
|
|
42
42
|
end
|
|
43
43
|
end
|
data/lib/zip/extra_field/ntfs.rb
CHANGED
|
@@ -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
|
|
|
@@ -19,14 +21,16 @@ module Zip
|
|
|
19
21
|
|
|
20
22
|
def merge(binstr)
|
|
21
23
|
return if binstr.empty?
|
|
24
|
+
|
|
22
25
|
size, content = initial_parse(binstr)
|
|
23
26
|
(size && content) || return
|
|
24
27
|
|
|
25
|
-
content = content[4
|
|
28
|
+
content = content[4..]
|
|
26
29
|
tags = parse_tags(content)
|
|
27
30
|
|
|
28
31
|
tag1 = tags[1]
|
|
29
32
|
return unless tag1
|
|
33
|
+
|
|
30
34
|
ntfs_mtime, ntfs_atime, ntfs_ctime = tag1.unpack('Q<Q<Q<')
|
|
31
35
|
ntfs_mtime && @mtime ||= from_ntfs_time(ntfs_mtime)
|
|
32
36
|
ntfs_atime && @atime ||= from_ntfs_time(ntfs_atime)
|
|
@@ -49,7 +53,7 @@ module Zip
|
|
|
49
53
|
# reserved 0 and tag 1
|
|
50
54
|
s = [0, 1].pack('Vv')
|
|
51
55
|
|
|
52
|
-
tag1 = ''.force_encoding(Encoding::BINARY)
|
|
56
|
+
tag1 = (+'').force_encoding(Encoding::BINARY)
|
|
53
57
|
if @mtime
|
|
54
58
|
tag1 << [to_ntfs_time(@mtime)].pack('Q<')
|
|
55
59
|
if @atime
|
|
@@ -65,12 +69,14 @@ module Zip
|
|
|
65
69
|
|
|
66
70
|
def parse_tags(content)
|
|
67
71
|
return {} if content.nil?
|
|
72
|
+
|
|
68
73
|
tags = {}
|
|
69
74
|
i = 0
|
|
70
75
|
while i < content.bytesize
|
|
71
76
|
tag, size = content[i, 4].unpack('vv')
|
|
72
77
|
i += 4
|
|
73
78
|
break unless tag && size
|
|
79
|
+
|
|
74
80
|
value = content[i, size]
|
|
75
81
|
i += size
|
|
76
82
|
tags[tag] = value
|
|
@@ -80,7 +86,7 @@ module Zip
|
|
|
80
86
|
end
|
|
81
87
|
|
|
82
88
|
def from_ntfs_time(ntfs_time)
|
|
83
|
-
::Zip::DOSTime.at(ntfs_time / WINDOWS_TICK - SEC_TO_UNIX_EPOCH)
|
|
89
|
+
::Zip::DOSTime.at((ntfs_time / WINDOWS_TICK) - SEC_TO_UNIX_EPOCH)
|
|
84
90
|
end
|
|
85
91
|
|
|
86
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
|
|
|
@@ -16,14 +18,16 @@ module Zip
|
|
|
16
18
|
|
|
17
19
|
def merge(binstr)
|
|
18
20
|
return if binstr.empty?
|
|
21
|
+
|
|
19
22
|
size, content = initial_parse(binstr)
|
|
20
23
|
# size: 0 for central directory. 4 for local header
|
|
21
24
|
return if !size || size == 0
|
|
25
|
+
|
|
22
26
|
atime, mtime, uid, gid = content.unpack('VVvv')
|
|
23
27
|
@uid ||= uid
|
|
24
28
|
@gid ||= gid
|
|
25
29
|
@atime ||= atime
|
|
26
|
-
@mtime ||= mtime
|
|
30
|
+
@mtime ||= mtime # rubocop:disable Naming/MemoizedInstanceVariableName
|
|
27
31
|
end
|
|
28
32
|
|
|
29
33
|
def ==(other)
|
|
@@ -1,27 +1,59 @@
|
|
|
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
|
|
|
9
|
+
ATIME_MASK = 0b010
|
|
10
|
+
CTIME_MASK = 0b100
|
|
11
|
+
MTIME_MASK = 0b001
|
|
12
|
+
|
|
7
13
|
def initialize(binstr = nil)
|
|
8
14
|
@ctime = nil
|
|
9
15
|
@mtime = nil
|
|
10
16
|
@atime = nil
|
|
11
|
-
@flag =
|
|
12
|
-
|
|
17
|
+
@flag = 0
|
|
18
|
+
|
|
19
|
+
merge(binstr) unless binstr.nil?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
attr_reader :atime, :ctime, :mtime, :flag
|
|
23
|
+
|
|
24
|
+
def atime=(time)
|
|
25
|
+
@flag = time.nil? ? @flag & ~ATIME_MASK : @flag | ATIME_MASK
|
|
26
|
+
@atime = time
|
|
13
27
|
end
|
|
14
28
|
|
|
15
|
-
|
|
29
|
+
def ctime=(time)
|
|
30
|
+
@flag = time.nil? ? @flag & ~CTIME_MASK : @flag | CTIME_MASK
|
|
31
|
+
@ctime = time
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def mtime=(time)
|
|
35
|
+
@flag = time.nil? ? @flag & ~MTIME_MASK : @flag | MTIME_MASK
|
|
36
|
+
@mtime = time
|
|
37
|
+
end
|
|
16
38
|
|
|
17
39
|
def merge(binstr)
|
|
18
40
|
return if binstr.empty?
|
|
41
|
+
|
|
19
42
|
size, content = initial_parse(binstr)
|
|
20
|
-
size ||
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
43
|
+
return if !size || size <= 0
|
|
44
|
+
|
|
45
|
+
@flag, *times = content.unpack('Cl<l<l<')
|
|
46
|
+
|
|
47
|
+
# Parse the timestamps, in order, based on which flags are set.
|
|
48
|
+
return if times[0].nil?
|
|
49
|
+
|
|
50
|
+
@mtime ||= ::Zip::DOSTime.at(times.shift) unless @flag & MTIME_MASK == 0
|
|
51
|
+
return if times[0].nil?
|
|
52
|
+
|
|
53
|
+
@atime ||= ::Zip::DOSTime.at(times.shift) unless @flag & ATIME_MASK == 0
|
|
54
|
+
return if times[0].nil?
|
|
55
|
+
|
|
56
|
+
@ctime ||= ::Zip::DOSTime.at(times.shift) unless @flag & CTIME_MASK == 0
|
|
25
57
|
end
|
|
26
58
|
|
|
27
59
|
def ==(other)
|
|
@@ -32,15 +64,15 @@ module Zip
|
|
|
32
64
|
|
|
33
65
|
def pack_for_local
|
|
34
66
|
s = [@flag].pack('C')
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
67
|
+
s << [@mtime.to_i].pack('l<') unless @flag & MTIME_MASK == 0
|
|
68
|
+
s << [@atime.to_i].pack('l<') unless @flag & ATIME_MASK == 0
|
|
69
|
+
s << [@ctime.to_i].pack('l<') unless @flag & CTIME_MASK == 0
|
|
38
70
|
s
|
|
39
71
|
end
|
|
40
72
|
|
|
41
73
|
def pack_for_c_dir
|
|
42
74
|
s = [@flag].pack('C')
|
|
43
|
-
|
|
75
|
+
s << [@mtime.to_i].pack('l<') unless @flag & MTIME_MASK == 0
|
|
44
76
|
s
|
|
45
77
|
end
|
|
46
78
|
end
|
data/lib/zip/extra_field/unix.rb
CHANGED
|
@@ -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
|
|
|
@@ -14,12 +16,14 @@ module Zip
|
|
|
14
16
|
|
|
15
17
|
def merge(binstr)
|
|
16
18
|
return if binstr.empty?
|
|
19
|
+
|
|
17
20
|
size, content = initial_parse(binstr)
|
|
18
21
|
# size: 0 for central directory. 4 for local header
|
|
19
22
|
return if !size || size == 0
|
|
23
|
+
|
|
20
24
|
uid, gid = content.unpack('vv')
|
|
21
|
-
@uid
|
|
22
|
-
@gid
|
|
25
|
+
@uid = uid
|
|
26
|
+
@gid = gid
|
|
23
27
|
end
|
|
24
28
|
|
|
25
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 :
|
|
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
|
|
|
@@ -9,7 +13,7 @@ module Zip
|
|
|
9
13
|
# unparsed binary; we don't actually know what this contains
|
|
10
14
|
# without looking for FFs in the associated file header
|
|
11
15
|
# call parse after initializing with a binary string
|
|
12
|
-
@content
|
|
16
|
+
@content = nil
|
|
13
17
|
@original_size = nil
|
|
14
18
|
@compressed_size = nil
|
|
15
19
|
@relative_header_offset = nil
|
|
@@ -26,6 +30,7 @@ module Zip
|
|
|
26
30
|
|
|
27
31
|
def merge(binstr)
|
|
28
32
|
return if binstr.empty?
|
|
33
|
+
|
|
29
34
|
_, @content = initial_parse(binstr)
|
|
30
35
|
end
|
|
31
36
|
|
|
@@ -35,7 +40,9 @@ module Zip
|
|
|
35
40
|
def parse(original_size, compressed_size, relative_header_offset = nil, disk_start_number = nil)
|
|
36
41
|
@original_size = extract(8, 'Q<') if original_size == 0xFFFFFFFF
|
|
37
42
|
@compressed_size = extract(8, 'Q<') if compressed_size == 0xFFFFFFFF
|
|
38
|
-
|
|
43
|
+
if relative_header_offset && relative_header_offset == 0xFFFFFFFF
|
|
44
|
+
@relative_header_offset = extract(8, 'Q<')
|
|
45
|
+
end
|
|
39
46
|
@disk_start_number = extract(4, 'V') if disk_start_number && disk_start_number == 0xFFFF
|
|
40
47
|
@content = nil
|
|
41
48
|
[@original_size || original_size,
|
|
@@ -45,19 +52,28 @@ module Zip
|
|
|
45
52
|
end
|
|
46
53
|
|
|
47
54
|
def extract(size, format)
|
|
48
|
-
@content.slice!(0, size).
|
|
55
|
+
@content.slice!(0, size).unpack1(format)
|
|
49
56
|
end
|
|
50
57
|
private :extract
|
|
51
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
|
+
|
|
52
66
|
def pack_for_local
|
|
53
|
-
#
|
|
67
|
+
# Local header entries must contain original size and compressed size;
|
|
68
|
+
# other fields do not apply.
|
|
54
69
|
return '' unless @original_size && @compressed_size
|
|
70
|
+
|
|
55
71
|
[@original_size, @compressed_size].pack('Q<Q<')
|
|
56
72
|
end
|
|
57
73
|
|
|
58
74
|
def pack_for_c_dir
|
|
59
75
|
# central directory entries contain only fields that didn't fit in the main entry part
|
|
60
|
-
packed = ''.force_encoding('BINARY')
|
|
76
|
+
packed = (+'').force_encoding('BINARY')
|
|
61
77
|
packed << [@original_size].pack('Q<') if @original_size
|
|
62
78
|
packed << [@compressed_size].pack('Q<') if @compressed_size
|
|
63
79
|
packed << [@relative_header_offset].pack('Q<') if @relative_header_offset
|