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.
- checksums.yaml +4 -4
- data/Changelog.md +476 -0
- data/LICENSE.md +24 -0
- data/README.md +180 -40
- data/Rakefile +15 -13
- data/lib/zip/central_directory.rb +172 -124
- data/lib/zip/compressor.rb +3 -1
- data/lib/zip/constants.rb +29 -21
- data/lib/zip/crypto/aes_encryption.rb +120 -0
- data/lib/zip/crypto/decrypted_io.rb +20 -14
- data/lib/zip/crypto/encryption.rb +4 -2
- data/lib/zip/crypto/null_encryption.rb +5 -13
- data/lib/zip/crypto/traditional_encryption.rb +10 -6
- data/lib/zip/decompressor.rb +4 -3
- data/lib/zip/deflater.rb +12 -8
- data/lib/zip/dirtyable.rb +32 -0
- data/lib/zip/dos_time.rb +45 -5
- data/lib/zip/entry.rb +391 -264
- data/lib/zip/entry_set.rb +11 -9
- data/lib/zip/errors.rb +136 -16
- data/lib/zip/extra_field/aes.rb +50 -0
- data/lib/zip/extra_field/generic.rb +10 -11
- data/lib/zip/extra_field/ntfs.rb +6 -4
- data/lib/zip/extra_field/old_unix.rb +3 -1
- data/lib/zip/extra_field/universal_time.rb +3 -1
- data/lib/zip/extra_field/unix.rb +5 -3
- data/lib/zip/extra_field/unknown.rb +35 -0
- data/lib/zip/extra_field/zip64.rb +19 -5
- data/lib/zip/extra_field.rb +25 -23
- data/lib/zip/file.rb +174 -267
- 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 +27 -596
- data/lib/zip/inflater.rb +11 -8
- data/lib/zip/input_stream.rb +76 -57
- data/lib/zip/ioextras/abstract_input_stream.rb +19 -13
- data/lib/zip/ioextras/abstract_output_stream.rb +13 -3
- data/lib/zip/ioextras.rb +7 -7
- data/lib/zip/null_compressor.rb +3 -1
- data/lib/zip/null_decompressor.rb +6 -3
- data/lib/zip/null_input_stream.rb +3 -1
- data/lib/zip/output_stream.rb +60 -57
- data/lib/zip/pass_thru_compressor.rb +3 -1
- data/lib/zip/pass_thru_decompressor.rb +8 -5
- data/lib/zip/streamable_directory.rb +3 -1
- data/lib/zip/streamable_stream.rb +4 -1
- data/lib/zip/version.rb +4 -1
- data/lib/zip.rb +25 -22
- data/rubyzip.gemspec +39 -0
- data/samples/example.rb +8 -3
- data/samples/example_filesystem.rb +3 -2
- data/samples/example_recursive.rb +3 -1
- data/samples/gtk_ruby_zip.rb +5 -3
- data/samples/qtzip.rb +7 -6
- data/samples/write_simple.rb +2 -1
- data/samples/zipfind.rb +1 -0
- metadata +86 -52
- data/TODO +0 -15
- data/lib/zip/extra_field/zip64_placeholder.rb +0 -15
|
@@ -1,45 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'forwardable'
|
|
4
|
+
|
|
5
|
+
require_relative 'dirtyable'
|
|
6
|
+
|
|
1
7
|
module Zip
|
|
2
|
-
class CentralDirectory
|
|
3
|
-
|
|
8
|
+
class CentralDirectory # :nodoc:
|
|
9
|
+
extend Forwardable
|
|
10
|
+
include Dirtyable
|
|
11
|
+
|
|
12
|
+
END_OF_CD_SIG = 0x06054b50
|
|
13
|
+
ZIP64_END_OF_CD_SIG = 0x06064b50
|
|
14
|
+
ZIP64_EOCD_LOCATOR_SIG = 0x07064b50
|
|
4
15
|
|
|
5
|
-
END_OF_CDS = 0x06054b50
|
|
6
|
-
ZIP64_END_OF_CDS = 0x06064b50
|
|
7
|
-
ZIP64_EOCD_LOCATOR = 0x07064b50
|
|
8
|
-
MAX_END_OF_CDS_SIZE = 65_536 + 18
|
|
9
16
|
STATIC_EOCD_SIZE = 22
|
|
17
|
+
ZIP64_STATIC_EOCD_SIZE = 56
|
|
18
|
+
ZIP64_EOCD_LOC_SIZE = 20
|
|
19
|
+
MAX_FILE_COMMENT_SIZE = (1 << 16) - 1
|
|
20
|
+
MAX_END_OF_CD_SIZE =
|
|
21
|
+
MAX_FILE_COMMENT_SIZE + STATIC_EOCD_SIZE + ZIP64_EOCD_LOC_SIZE
|
|
10
22
|
|
|
11
|
-
|
|
23
|
+
attr_accessor :comment
|
|
12
24
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
25
|
+
def_delegators :@entry_set,
|
|
26
|
+
:<<, :delete, :each, :entries, :find_entry, :glob,
|
|
27
|
+
:include?, :size
|
|
28
|
+
|
|
29
|
+
mark_dirty :<<, :comment=, :delete
|
|
17
30
|
|
|
18
|
-
def initialize(entries = EntrySet.new, comment = '')
|
|
19
|
-
super()
|
|
31
|
+
def initialize(entries = EntrySet.new, comment = '') # :nodoc:
|
|
32
|
+
super(dirty_on_create: false)
|
|
20
33
|
@entry_set = entries.kind_of?(EntrySet) ? entries : EntrySet.new(entries)
|
|
21
34
|
@comment = comment
|
|
22
35
|
end
|
|
23
36
|
|
|
24
|
-
def
|
|
37
|
+
def read_from_stream(io)
|
|
38
|
+
read_eocds(io)
|
|
39
|
+
read_central_directory_entries(io)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def write_to_stream(io, suppress_extra_fields: false) # :nodoc:
|
|
25
43
|
cdir_offset = io.tell
|
|
26
|
-
@entry_set.each
|
|
44
|
+
@entry_set.each do |entry|
|
|
45
|
+
entry.write_c_dir_entry(io, suppress_extra_fields: suppress_extra_fields)
|
|
46
|
+
end
|
|
27
47
|
eocd_offset = io.tell
|
|
28
48
|
cdir_size = eocd_offset - cdir_offset
|
|
29
|
-
if
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
write_64_e_o_c_d(io, cdir_offset, cdir_size)
|
|
34
|
-
write_64_eocd_locator(io, eocd_offset)
|
|
35
|
-
end
|
|
49
|
+
if Zip.write_zip64_support &&
|
|
50
|
+
(cdir_offset > 0xFFFFFFFF || cdir_size > 0xFFFFFFFF || @entry_set.size > 0xFFFF)
|
|
51
|
+
write_64_e_o_c_d(io, cdir_offset, cdir_size)
|
|
52
|
+
write_64_eocd_locator(io, eocd_offset)
|
|
36
53
|
end
|
|
37
54
|
write_e_o_c_d(io, cdir_offset, cdir_size)
|
|
38
55
|
end
|
|
39
56
|
|
|
40
|
-
|
|
57
|
+
# Reads the End of Central Directory Record (and the Zip64 equivalent if
|
|
58
|
+
# needs be) and returns the number of entries in the archive. This is a
|
|
59
|
+
# convenience method that avoids reading in all of the entry data to get a
|
|
60
|
+
# very quick entry count.
|
|
61
|
+
def count_entries(io)
|
|
62
|
+
read_eocds(io)
|
|
63
|
+
@size
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def ==(other) # :nodoc:
|
|
67
|
+
return false unless other.kind_of?(CentralDirectory)
|
|
68
|
+
|
|
69
|
+
@entry_set.entries.sort == other.entries.sort && comment == other.comment
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def write_e_o_c_d(io, offset, cdir_size) # :nodoc:
|
|
41
75
|
tmp = [
|
|
42
|
-
|
|
76
|
+
END_OF_CD_SIG,
|
|
43
77
|
0, # @numberOfThisDisk
|
|
44
78
|
0, # @numberOfDiskWithStartOfCDir
|
|
45
79
|
@entry_set ? [@entry_set.size, 0xFFFF].min : 0,
|
|
@@ -52,11 +86,9 @@ module Zip
|
|
|
52
86
|
io << @comment
|
|
53
87
|
end
|
|
54
88
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def write_64_e_o_c_d(io, offset, cdir_size) #:nodoc:
|
|
89
|
+
def write_64_e_o_c_d(io, offset, cdir_size) # :nodoc:
|
|
58
90
|
tmp = [
|
|
59
|
-
|
|
91
|
+
ZIP64_END_OF_CD_SIG,
|
|
60
92
|
44, # size of zip64 end of central directory record (excludes signature and field itself)
|
|
61
93
|
VERSION_MADE_BY,
|
|
62
94
|
VERSION_NEEDED_TO_EXTRACT_ZIP64,
|
|
@@ -70,11 +102,9 @@ module Zip
|
|
|
70
102
|
io << tmp.pack('VQ<vvVVQ<Q<Q<Q<')
|
|
71
103
|
end
|
|
72
104
|
|
|
73
|
-
private :write_64_e_o_c_d
|
|
74
|
-
|
|
75
105
|
def write_64_eocd_locator(io, zip64_eocd_offset)
|
|
76
106
|
tmp = [
|
|
77
|
-
|
|
107
|
+
ZIP64_EOCD_LOCATOR_SIG,
|
|
78
108
|
0, # number of disk containing the start of zip64 eocd record
|
|
79
109
|
zip64_eocd_offset, # offset of the start of zip64 eocd record in its disk
|
|
80
110
|
1 # total number of disks
|
|
@@ -82,127 +112,145 @@ module Zip
|
|
|
82
112
|
io << tmp.pack('VVQ<V')
|
|
83
113
|
end
|
|
84
114
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
@
|
|
90
|
-
@
|
|
91
|
-
@
|
|
92
|
-
@
|
|
93
|
-
@
|
|
94
|
-
@
|
|
95
|
-
@
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
115
|
+
def unpack_64_e_o_c_d(buffer) # :nodoc:
|
|
116
|
+
_, # ZIP64_END_OF_CD_SIG. We know we have this at this point.
|
|
117
|
+
@size_of_zip64_e_o_c_d,
|
|
118
|
+
@version_made_by,
|
|
119
|
+
@version_needed_for_extract,
|
|
120
|
+
@number_of_this_disk,
|
|
121
|
+
@number_of_disk_with_start_of_cdir,
|
|
122
|
+
@total_number_of_entries_in_cdir_on_this_disk,
|
|
123
|
+
@size,
|
|
124
|
+
@size_in_bytes,
|
|
125
|
+
@cdir_offset = buffer.unpack('VQ<vvVVQ<Q<Q<Q<')
|
|
126
|
+
|
|
127
|
+
zip64_extensible_data_size =
|
|
128
|
+
@size_of_zip64_e_o_c_d - ZIP64_STATIC_EOCD_SIZE + 12
|
|
129
|
+
@zip64_extensible_data = if zip64_extensible_data_size.zero?
|
|
130
|
+
''
|
|
131
|
+
else
|
|
132
|
+
buffer.slice(
|
|
133
|
+
ZIP64_STATIC_EOCD_SIZE,
|
|
134
|
+
zip64_extensible_data_size
|
|
135
|
+
)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def unpack_64_eocd_locator(buffer) # :nodoc:
|
|
140
|
+
_, # ZIP64_EOCD_LOCATOR_SIG. We know we have this at this point.
|
|
141
|
+
_, zip64_eocd_offset, = buffer.unpack('VVQ<V')
|
|
142
|
+
|
|
143
|
+
zip64_eocd_offset
|
|
100
144
|
end
|
|
101
145
|
|
|
102
|
-
def
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
comment_length
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
146
|
+
def unpack_e_o_c_d(buffer) # :nodoc:
|
|
147
|
+
_, # END_OF_CD_SIG. We know we have this at this point.
|
|
148
|
+
num_disk,
|
|
149
|
+
num_disk_cdir,
|
|
150
|
+
num_cdir_disk,
|
|
151
|
+
num_entries,
|
|
152
|
+
size_in_bytes,
|
|
153
|
+
cdir_offset,
|
|
154
|
+
comment_length = buffer.unpack('VvvvvVVv')
|
|
155
|
+
|
|
156
|
+
@number_of_this_disk = num_disk unless num_disk == 0xFFFF
|
|
157
|
+
@number_of_disk_with_start_of_cdir = num_disk_cdir unless num_disk_cdir == 0xFFFF
|
|
158
|
+
@total_number_of_entries_in_cdir_on_this_disk = num_cdir_disk unless num_cdir_disk == 0xFFFF
|
|
159
|
+
@size = num_entries unless num_entries == 0xFFFF
|
|
160
|
+
@size_in_bytes = size_in_bytes unless size_in_bytes == 0xFFFFFFFF
|
|
161
|
+
@cdir_offset = cdir_offset unless cdir_offset == 0xFFFFFFFF
|
|
162
|
+
|
|
163
|
+
@comment = if comment_length.positive?
|
|
164
|
+
buffer.slice(STATIC_EOCD_SIZE, comment_length)
|
|
165
|
+
else
|
|
166
|
+
''
|
|
167
|
+
end
|
|
117
168
|
end
|
|
118
169
|
|
|
119
|
-
def read_central_directory_entries(io)
|
|
170
|
+
def read_central_directory_entries(io) # :nodoc:
|
|
171
|
+
# `StringIO` doesn't raise `EINVAL` if you seek beyond the current end,
|
|
172
|
+
# so we need to catch that *and* query `io#eof?` here.
|
|
173
|
+
eof = false
|
|
120
174
|
begin
|
|
121
175
|
io.seek(@cdir_offset, IO::SEEK_SET)
|
|
122
176
|
rescue Errno::EINVAL
|
|
123
|
-
|
|
177
|
+
eof = true
|
|
124
178
|
end
|
|
179
|
+
raise Error, 'Zip consistency problem while reading central directory entry' if eof || io.eof?
|
|
180
|
+
|
|
125
181
|
@entry_set = EntrySet.new
|
|
126
182
|
@size.times do
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
183
|
+
entry = Entry.read_c_dir_entry(io)
|
|
184
|
+
next unless entry
|
|
185
|
+
|
|
186
|
+
offset = if entry.zip64?
|
|
187
|
+
entry.extra[:zip64].relative_header_offset
|
|
188
|
+
else
|
|
189
|
+
entry.local_header_offset
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
unless offset.nil?
|
|
193
|
+
io_save = io.tell
|
|
194
|
+
io.seek(offset, IO::SEEK_SET)
|
|
195
|
+
entry.read_extra_field(read_local_extra_field(io), local: true)
|
|
196
|
+
io.seek(io_save, IO::SEEK_SET)
|
|
197
|
+
end
|
|
130
198
|
|
|
131
|
-
|
|
132
|
-
buf = start_buf(io)
|
|
133
|
-
if zip64_file?(buf)
|
|
134
|
-
read_64_e_o_c_d(buf)
|
|
135
|
-
else
|
|
136
|
-
read_e_o_c_d(buf)
|
|
199
|
+
@entry_set << entry
|
|
137
200
|
end
|
|
138
|
-
read_central_directory_entries(io)
|
|
139
201
|
end
|
|
140
202
|
|
|
141
|
-
def
|
|
142
|
-
|
|
143
|
-
|
|
203
|
+
def read_local_extra_field(io)
|
|
204
|
+
buf = io.read(::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH) || ''
|
|
205
|
+
return '' unless buf.bytesize == ::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH
|
|
144
206
|
|
|
145
|
-
|
|
207
|
+
head, _, _, _, _, _, _, _, _, _, n_len, e_len = buf.unpack('VCCvvvvVVVvv')
|
|
208
|
+
return '' unless head == ::Zip::LOCAL_ENTRY_SIGNATURE
|
|
146
209
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
buf
|
|
210
|
+
io.seek(n_len, IO::SEEK_CUR) # Skip over the entry name.
|
|
211
|
+
io.read(e_len)
|
|
152
212
|
end
|
|
153
213
|
|
|
154
|
-
def
|
|
155
|
-
|
|
156
|
-
end
|
|
214
|
+
def read_eocds(io) # :nodoc:
|
|
215
|
+
base_location, data = eocd_data(io)
|
|
157
216
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
io.seek(-MAX_END_OF_CDS_SIZE, IO::SEEK_END)
|
|
161
|
-
rescue Errno::EINVAL
|
|
162
|
-
io.seek(0, IO::SEEK_SET)
|
|
163
|
-
end
|
|
164
|
-
io.read
|
|
165
|
-
end
|
|
217
|
+
eocd_location = data.rindex([END_OF_CD_SIG].pack('V'))
|
|
218
|
+
raise Error, 'Zip end of central directory signature not found' unless eocd_location
|
|
166
219
|
|
|
167
|
-
|
|
168
|
-
zip_64_start = buf.rindex([ZIP64_END_OF_CDS].pack('V'))
|
|
169
|
-
raise Error, 'Zip64 end of central directory signature not found' unless zip_64_start
|
|
220
|
+
zip64_eocd_locator = data.rindex([ZIP64_EOCD_LOCATOR_SIG].pack('V'))
|
|
170
221
|
|
|
171
|
-
|
|
172
|
-
|
|
222
|
+
if zip64_eocd_locator
|
|
223
|
+
zip64_eocd_location = data.rindex([ZIP64_END_OF_CD_SIG].pack('V'))
|
|
173
224
|
|
|
174
|
-
|
|
225
|
+
zip64_eocd_data =
|
|
226
|
+
if zip64_eocd_location
|
|
227
|
+
data.slice(zip64_eocd_location..zip64_eocd_locator)
|
|
228
|
+
else
|
|
229
|
+
zip64_eocd_location = unpack_64_eocd_locator(
|
|
230
|
+
data.slice(zip64_eocd_locator..eocd_location)
|
|
231
|
+
)
|
|
232
|
+
unless zip64_eocd_location
|
|
233
|
+
raise Error, 'Zip64 end of central directory signature not found'
|
|
234
|
+
end
|
|
175
235
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
236
|
+
io.seek(zip64_eocd_location, IO::SEEK_SET)
|
|
237
|
+
io.read(base_location + zip64_eocd_locator - zip64_eocd_location)
|
|
238
|
+
end
|
|
179
239
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
# For iterating over the entries.
|
|
184
|
-
def each(&a_proc)
|
|
185
|
-
@entry_set.each(&a_proc)
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
# Returns the number of entries in the central directory (and
|
|
189
|
-
# consequently in the zip archive).
|
|
190
|
-
def size
|
|
191
|
-
@entry_set.size
|
|
192
|
-
end
|
|
240
|
+
unpack_64_e_o_c_d(zip64_eocd_data)
|
|
241
|
+
end
|
|
193
242
|
|
|
194
|
-
|
|
195
|
-
cdir = new
|
|
196
|
-
cdir.read_from_stream(io)
|
|
197
|
-
cdir
|
|
198
|
-
rescue Error
|
|
199
|
-
nil
|
|
243
|
+
unpack_e_o_c_d(data.slice(eocd_location..-1))
|
|
200
244
|
end
|
|
201
245
|
|
|
202
|
-
def
|
|
203
|
-
|
|
246
|
+
def eocd_data(io)
|
|
247
|
+
begin
|
|
248
|
+
io.seek(-MAX_END_OF_CD_SIZE, IO::SEEK_END)
|
|
249
|
+
rescue Errno::EINVAL
|
|
250
|
+
io.seek(0, IO::SEEK_SET)
|
|
251
|
+
end
|
|
204
252
|
|
|
205
|
-
|
|
253
|
+
[io.tell, io.read]
|
|
206
254
|
end
|
|
207
255
|
end
|
|
208
256
|
end
|
data/lib/zip/compressor.rb
CHANGED
data/lib/zip/constants.rb
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Zip
|
|
4
|
+
# :stopdoc:
|
|
5
|
+
|
|
2
6
|
RUNNING_ON_WINDOWS = RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/i
|
|
3
7
|
|
|
4
8
|
CENTRAL_DIRECTORY_ENTRY_SIGNATURE = 0x02014b50
|
|
@@ -11,6 +15,8 @@ module Zip
|
|
|
11
15
|
VERSION_NEEDED_TO_EXTRACT = 20
|
|
12
16
|
VERSION_NEEDED_TO_EXTRACT_ZIP64 = 45
|
|
13
17
|
|
|
18
|
+
SPLIT_FILE_SIGNATURE = 0x08074b50
|
|
19
|
+
|
|
14
20
|
FILE_TYPE_FILE = 0o10
|
|
15
21
|
FILE_TYPE_DIR = 0o04
|
|
16
22
|
FILE_TYPE_SYMLINK = 0o12
|
|
@@ -38,27 +44,27 @@ module Zip
|
|
|
38
44
|
FSTYPE_ATHEOS = 30
|
|
39
45
|
|
|
40
46
|
FSTYPES = {
|
|
41
|
-
FSTYPE_FAT => 'FAT'
|
|
42
|
-
FSTYPE_AMIGA => 'Amiga'
|
|
43
|
-
FSTYPE_VMS => 'VMS (Vax or Alpha AXP)'
|
|
44
|
-
FSTYPE_UNIX => 'Unix'
|
|
45
|
-
FSTYPE_VM_CMS => 'VM/CMS'
|
|
46
|
-
FSTYPE_ATARI => 'Atari ST'
|
|
47
|
-
FSTYPE_HPFS => 'OS/2 or NT HPFS'
|
|
48
|
-
FSTYPE_MAC => 'Macintosh'
|
|
49
|
-
FSTYPE_Z_SYSTEM => 'Z-System'
|
|
50
|
-
FSTYPE_CPM => 'CP/M'
|
|
51
|
-
FSTYPE_TOPS20 => 'TOPS-20'
|
|
52
|
-
FSTYPE_NTFS => 'NTFS'
|
|
53
|
-
FSTYPE_QDOS => 'SMS/QDOS'
|
|
54
|
-
FSTYPE_ACORN => 'Acorn RISC OS'
|
|
55
|
-
FSTYPE_VFAT => 'Win32 VFAT'
|
|
56
|
-
FSTYPE_MVS => 'MVS'
|
|
57
|
-
FSTYPE_BEOS => 'BeOS'
|
|
58
|
-
FSTYPE_TANDEM => 'Tandem NSK'
|
|
59
|
-
FSTYPE_THEOS => 'Theos'
|
|
60
|
-
FSTYPE_MAC_OSX => 'Mac OS/X (Darwin)'
|
|
61
|
-
FSTYPE_ATHEOS => 'AtheOS'
|
|
47
|
+
FSTYPE_FAT => 'FAT',
|
|
48
|
+
FSTYPE_AMIGA => 'Amiga',
|
|
49
|
+
FSTYPE_VMS => 'VMS (Vax or Alpha AXP)',
|
|
50
|
+
FSTYPE_UNIX => 'Unix',
|
|
51
|
+
FSTYPE_VM_CMS => 'VM/CMS',
|
|
52
|
+
FSTYPE_ATARI => 'Atari ST',
|
|
53
|
+
FSTYPE_HPFS => 'OS/2 or NT HPFS',
|
|
54
|
+
FSTYPE_MAC => 'Macintosh',
|
|
55
|
+
FSTYPE_Z_SYSTEM => 'Z-System',
|
|
56
|
+
FSTYPE_CPM => 'CP/M',
|
|
57
|
+
FSTYPE_TOPS20 => 'TOPS-20',
|
|
58
|
+
FSTYPE_NTFS => 'NTFS',
|
|
59
|
+
FSTYPE_QDOS => 'SMS/QDOS',
|
|
60
|
+
FSTYPE_ACORN => 'Acorn RISC OS',
|
|
61
|
+
FSTYPE_VFAT => 'Win32 VFAT',
|
|
62
|
+
FSTYPE_MVS => 'MVS',
|
|
63
|
+
FSTYPE_BEOS => 'BeOS',
|
|
64
|
+
FSTYPE_TANDEM => 'Tandem NSK',
|
|
65
|
+
FSTYPE_THEOS => 'Theos',
|
|
66
|
+
FSTYPE_MAC_OSX => 'Mac OS/X (Darwin)',
|
|
67
|
+
FSTYPE_ATHEOS => 'AtheOS'
|
|
62
68
|
}.freeze
|
|
63
69
|
|
|
64
70
|
COMPRESSION_METHOD_STORE = 0
|
|
@@ -112,4 +118,6 @@ module Zip
|
|
|
112
118
|
COMPRESSION_METHOD_PPMD => 'PPMd version I, Rev 1',
|
|
113
119
|
COMPRESSION_METHOD_AES => 'AES encryption'
|
|
114
120
|
}.freeze
|
|
121
|
+
|
|
122
|
+
# :startdoc:
|
|
115
123
|
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'openssl'
|
|
4
|
+
|
|
5
|
+
module Zip
|
|
6
|
+
module AESEncryption # :nodoc:
|
|
7
|
+
VERIFIER_LENGTH = 2
|
|
8
|
+
BLOCK_SIZE = 16
|
|
9
|
+
AUTHENTICATION_CODE_LENGTH = 10
|
|
10
|
+
|
|
11
|
+
VERSION_AE_1 = 0x01
|
|
12
|
+
VERSION_AE_2 = 0x02
|
|
13
|
+
|
|
14
|
+
VERSIONS = [
|
|
15
|
+
VERSION_AE_1,
|
|
16
|
+
VERSION_AE_2
|
|
17
|
+
].freeze
|
|
18
|
+
|
|
19
|
+
STRENGTH_128_BIT = 0x01
|
|
20
|
+
STRENGTH_192_BIT = 0x02
|
|
21
|
+
STRENGTH_256_BIT = 0x03
|
|
22
|
+
|
|
23
|
+
STRENGTHS = [
|
|
24
|
+
STRENGTH_128_BIT,
|
|
25
|
+
STRENGTH_192_BIT,
|
|
26
|
+
STRENGTH_256_BIT
|
|
27
|
+
].freeze
|
|
28
|
+
|
|
29
|
+
BITS = {
|
|
30
|
+
STRENGTH_128_BIT => 128,
|
|
31
|
+
STRENGTH_192_BIT => 192,
|
|
32
|
+
STRENGTH_256_BIT => 256
|
|
33
|
+
}.freeze
|
|
34
|
+
|
|
35
|
+
KEY_LENGTHS = {
|
|
36
|
+
STRENGTH_128_BIT => 16,
|
|
37
|
+
STRENGTH_192_BIT => 24,
|
|
38
|
+
STRENGTH_256_BIT => 32
|
|
39
|
+
}.freeze
|
|
40
|
+
|
|
41
|
+
SALT_LENGTHS = {
|
|
42
|
+
STRENGTH_128_BIT => 8,
|
|
43
|
+
STRENGTH_192_BIT => 12,
|
|
44
|
+
STRENGTH_256_BIT => 16
|
|
45
|
+
}.freeze
|
|
46
|
+
|
|
47
|
+
def initialize(password, strength)
|
|
48
|
+
@password = password
|
|
49
|
+
@strength = strength
|
|
50
|
+
@bits = BITS[@strength]
|
|
51
|
+
@key_length = KEY_LENGTHS[@strength]
|
|
52
|
+
@salt_length = SALT_LENGTHS[@strength]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def header_bytesize
|
|
56
|
+
@salt_length + VERIFIER_LENGTH
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def gp_flags
|
|
60
|
+
0x0001
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
class AESDecrypter < Decrypter # :nodoc:
|
|
65
|
+
include AESEncryption
|
|
66
|
+
|
|
67
|
+
def decrypt(encrypted_data)
|
|
68
|
+
@hmac.update(encrypted_data)
|
|
69
|
+
|
|
70
|
+
idx = 0
|
|
71
|
+
decrypted_data = +''
|
|
72
|
+
amount_to_read = encrypted_data.size
|
|
73
|
+
|
|
74
|
+
while amount_to_read.positive?
|
|
75
|
+
@cipher.iv = [@counter + 1].pack('Vx12')
|
|
76
|
+
begin_index = BLOCK_SIZE * idx
|
|
77
|
+
end_index = begin_index + [BLOCK_SIZE, amount_to_read].min
|
|
78
|
+
decrypted_data << @cipher.update(encrypted_data[begin_index...end_index])
|
|
79
|
+
amount_to_read -= BLOCK_SIZE
|
|
80
|
+
@counter += 1
|
|
81
|
+
idx += 1
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# JRuby requires finalization of the cipher. This is a bug, as noted in
|
|
85
|
+
# jruby/jruby-openssl#182 and jruby/jruby-openssl#183.
|
|
86
|
+
decrypted_data << @cipher.final if defined?(JRUBY_VERSION)
|
|
87
|
+
decrypted_data
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def reset!(header)
|
|
91
|
+
raise Error, "Unsupported encryption AES-#{@bits}" unless STRENGTHS.include? @strength
|
|
92
|
+
|
|
93
|
+
salt = header[0...@salt_length]
|
|
94
|
+
pwd_verify = header[-VERIFIER_LENGTH..]
|
|
95
|
+
key_material = OpenSSL::KDF.pbkdf2_hmac(
|
|
96
|
+
@password,
|
|
97
|
+
salt: salt,
|
|
98
|
+
iterations: 1000,
|
|
99
|
+
length: (2 * @key_length) + VERIFIER_LENGTH,
|
|
100
|
+
hash: 'sha1'
|
|
101
|
+
)
|
|
102
|
+
enc_key = key_material[0...@key_length]
|
|
103
|
+
enc_hmac_key = key_material[@key_length...(2 * @key_length)]
|
|
104
|
+
enc_pwd_verify = key_material[-VERIFIER_LENGTH..]
|
|
105
|
+
|
|
106
|
+
raise Error, 'Bad password' if enc_pwd_verify != pwd_verify
|
|
107
|
+
|
|
108
|
+
@counter = 0
|
|
109
|
+
@cipher = OpenSSL::Cipher::AES.new(@bits, :CTR)
|
|
110
|
+
@cipher.decrypt
|
|
111
|
+
@cipher.key = enc_key
|
|
112
|
+
@hmac = OpenSSL::HMAC.new(enc_hmac_key, OpenSSL::Digest.new('SHA1'))
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def check_integrity!(io)
|
|
116
|
+
auth_code = io.read(AUTHENTICATION_CODE_LENGTH)
|
|
117
|
+
raise Error, 'Integrity fault' if @hmac.digest[0...AUTHENTICATION_CODE_LENGTH] != auth_code
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -1,40 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Zip
|
|
2
|
-
class DecryptedIo
|
|
4
|
+
class DecryptedIo # :nodoc:all
|
|
3
5
|
CHUNK_SIZE = 32_768
|
|
4
6
|
|
|
5
|
-
def initialize(io, decrypter)
|
|
7
|
+
def initialize(io, decrypter, compressed_size)
|
|
6
8
|
@io = io
|
|
7
9
|
@decrypter = decrypter
|
|
10
|
+
@bytes_remaining = compressed_size
|
|
11
|
+
@buffer = +''
|
|
8
12
|
end
|
|
9
13
|
|
|
10
14
|
def read(length = nil, outbuf = +'')
|
|
11
|
-
return (length.nil? || length.zero? ? '' : nil) if eof
|
|
15
|
+
return (length.nil? || length.zero? ? '' : nil) if eof?
|
|
12
16
|
|
|
13
|
-
while length.nil? || (buffer.bytesize < length)
|
|
17
|
+
while length.nil? || (@buffer.bytesize < length)
|
|
14
18
|
break if input_finished?
|
|
15
19
|
|
|
16
|
-
buffer << produce_input
|
|
20
|
+
@buffer << produce_input
|
|
17
21
|
end
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
@decrypter.check_integrity!(@io) if input_finished?
|
|
24
|
+
|
|
25
|
+
outbuf.replace(@buffer.slice!(0...(length || @buffer.bytesize)))
|
|
20
26
|
end
|
|
21
27
|
|
|
22
28
|
private
|
|
23
29
|
|
|
24
|
-
def eof
|
|
25
|
-
buffer.empty? && input_finished?
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def buffer
|
|
29
|
-
@buffer ||= +''
|
|
30
|
+
def eof?
|
|
31
|
+
@buffer.empty? && input_finished?
|
|
30
32
|
end
|
|
31
33
|
|
|
32
34
|
def input_finished?
|
|
33
|
-
|
|
35
|
+
!@bytes_remaining.positive?
|
|
34
36
|
end
|
|
35
37
|
|
|
36
38
|
def produce_input
|
|
37
|
-
@
|
|
39
|
+
chunk_size = [@bytes_remaining, CHUNK_SIZE].min
|
|
40
|
+
return '' unless chunk_size.positive?
|
|
41
|
+
|
|
42
|
+
@bytes_remaining -= chunk_size
|
|
43
|
+
@decrypter.decrypt(@io.read(chunk_size))
|
|
38
44
|
end
|
|
39
45
|
end
|
|
40
46
|
end
|