rubyzip 0.9.9 → 1.3.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 +7 -0
- data/README.md +284 -41
- data/Rakefile +11 -6
- data/TODO +0 -1
- data/lib/zip/central_directory.rb +208 -0
- data/lib/zip/compressor.rb +1 -2
- data/lib/zip/constants.rb +59 -7
- data/lib/zip/crypto/encryption.rb +11 -0
- data/lib/zip/crypto/null_encryption.rb +43 -0
- data/lib/zip/crypto/traditional_encryption.rb +99 -0
- data/lib/zip/decompressor.rb +4 -4
- data/lib/zip/deflater.rb +17 -13
- data/lib/zip/dos_time.rb +13 -14
- data/lib/zip/entry.rb +700 -0
- data/lib/zip/entry_set.rb +86 -0
- data/lib/zip/errors.rb +18 -0
- data/lib/zip/extra_field/generic.rb +43 -0
- data/lib/zip/extra_field/ntfs.rb +90 -0
- data/lib/zip/extra_field/old_unix.rb +44 -0
- data/lib/zip/extra_field/universal_time.rb +47 -0
- data/lib/zip/extra_field/unix.rb +37 -0
- data/lib/zip/extra_field/zip64.rb +68 -0
- data/lib/zip/extra_field/zip64_placeholder.rb +15 -0
- data/lib/zip/extra_field.rb +101 -0
- data/lib/zip/file.rb +443 -0
- data/lib/zip/{zipfilesystem.rb → filesystem.rb} +162 -157
- data/lib/zip/inflater.rb +29 -28
- data/lib/zip/input_stream.rb +173 -0
- data/lib/zip/ioextras/abstract_input_stream.rb +111 -0
- data/lib/zip/ioextras/abstract_output_stream.rb +43 -0
- data/lib/zip/ioextras.rb +21 -149
- data/lib/zip/null_compressor.rb +2 -2
- data/lib/zip/null_decompressor.rb +8 -6
- data/lib/zip/null_input_stream.rb +3 -2
- data/lib/zip/output_stream.rb +189 -0
- data/lib/zip/pass_thru_compressor.rb +6 -6
- data/lib/zip/pass_thru_decompressor.rb +19 -19
- data/lib/zip/{zip_streamable_directory.rb → streamable_directory.rb} +3 -3
- data/lib/zip/streamable_stream.rb +56 -0
- data/lib/zip/version.rb +3 -0
- data/lib/zip.rb +71 -0
- data/samples/example.rb +44 -32
- data/samples/example_filesystem.rb +16 -18
- data/samples/example_recursive.rb +33 -28
- data/samples/{gtkRubyzip.rb → gtk_ruby_zip.rb} +26 -28
- data/samples/qtzip.rb +22 -31
- data/samples/write_simple.rb +12 -13
- data/samples/zipfind.rb +31 -39
- data/test/basic_zip_file_test.rb +60 -0
- data/test/case_sensitivity_test.rb +69 -0
- data/test/central_directory_entry_test.rb +69 -0
- data/test/central_directory_test.rb +100 -0
- data/test/crypto/null_encryption_test.rb +57 -0
- data/test/crypto/traditional_encryption_test.rb +80 -0
- data/test/data/WarnInvalidDate.zip +0 -0
- data/test/data/file1.txt +46 -0
- data/test/data/file1.txt.deflatedData +0 -0
- data/test/data/file2.txt +1504 -0
- 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 +1 -0
- data/test/data/notzippedruby.rb +7 -0
- data/test/data/ntfs.zip +0 -0
- data/test/data/oddExtraField.zip +0 -0
- data/test/data/path_traversal/Makefile +10 -0
- data/test/data/path_traversal/jwilk/README.md +5 -0
- 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 +3 -0
- 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 +65 -0
- data/test/encryption_test.rb +42 -0
- data/test/entry_set_test.rb +163 -0
- data/test/entry_test.rb +154 -0
- data/test/errors_test.rb +35 -0
- data/test/extra_field_test.rb +76 -0
- data/test/file_extract_directory_test.rb +54 -0
- data/test/file_extract_test.rb +145 -0
- data/test/file_permissions_test.rb +65 -0
- data/test/file_split_test.rb +57 -0
- data/test/file_test.rb +666 -0
- data/test/filesystem/dir_iterator_test.rb +58 -0
- data/test/filesystem/directory_test.rb +139 -0
- data/test/filesystem/file_mutating_test.rb +87 -0
- data/test/filesystem/file_nonmutating_test.rb +508 -0
- data/test/filesystem/file_stat_test.rb +64 -0
- data/test/gentestfiles.rb +126 -0
- data/test/inflater_test.rb +14 -0
- data/test/input_stream_test.rb +182 -0
- data/test/ioextras/abstract_input_stream_test.rb +102 -0
- data/test/ioextras/abstract_output_stream_test.rb +106 -0
- data/test/ioextras/fake_io_test.rb +18 -0
- data/test/local_entry_test.rb +154 -0
- data/test/output_stream_test.rb +128 -0
- data/test/pass_thru_compressor_test.rb +30 -0
- data/test/pass_thru_decompressor_test.rb +14 -0
- data/test/path_traversal_test.rb +141 -0
- data/test/samples/example_recursive_test.rb +37 -0
- data/test/settings_test.rb +95 -0
- data/test/test_helper.rb +234 -0
- data/test/unicode_file_names_and_comments_test.rb +62 -0
- data/test/zip64_full_test.rb +51 -0
- data/test/zip64_support_test.rb +14 -0
- metadata +274 -41
- data/NEWS +0 -172
- data/lib/zip/settings.rb +0 -10
- data/lib/zip/tempfile_bugfixed.rb +0 -195
- data/lib/zip/zip.rb +0 -56
- data/lib/zip/zip_central_directory.rb +0 -135
- data/lib/zip/zip_entry.rb +0 -638
- data/lib/zip/zip_entry_set.rb +0 -77
- data/lib/zip/zip_extra_field.rb +0 -213
- data/lib/zip/zip_file.rb +0 -340
- data/lib/zip/zip_input_stream.rb +0 -144
- data/lib/zip/zip_output_stream.rb +0 -173
- data/lib/zip/zip_streamable_stream.rb +0 -47
data/lib/zip/entry.rb
ADDED
@@ -0,0 +1,700 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
module Zip
|
3
|
+
class Entry
|
4
|
+
STORED = 0
|
5
|
+
DEFLATED = 8
|
6
|
+
# Language encoding flag (EFS) bit
|
7
|
+
EFS = 0b100000000000
|
8
|
+
|
9
|
+
attr_accessor :comment, :compressed_size, :crc, :extra, :compression_method,
|
10
|
+
:name, :size, :local_header_offset, :zipfile, :fstype, :external_file_attributes,
|
11
|
+
:internal_file_attributes,
|
12
|
+
:gp_flags, :header_signature, :follow_symlinks,
|
13
|
+
:restore_times, :restore_permissions, :restore_ownership,
|
14
|
+
:unix_uid, :unix_gid, :unix_perms,
|
15
|
+
:dirty
|
16
|
+
attr_reader :ftype, :filepath # :nodoc:
|
17
|
+
|
18
|
+
def set_default_vars_values
|
19
|
+
@local_header_offset = 0
|
20
|
+
@local_header_size = nil # not known until local entry is created or read
|
21
|
+
@internal_file_attributes = 1
|
22
|
+
@external_file_attributes = 0
|
23
|
+
@header_signature = ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
|
24
|
+
|
25
|
+
@version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT
|
26
|
+
@version = VERSION_MADE_BY
|
27
|
+
|
28
|
+
@ftype = nil # unspecified or unknown
|
29
|
+
@filepath = nil
|
30
|
+
@gp_flags = 0
|
31
|
+
if ::Zip.unicode_names
|
32
|
+
@gp_flags |= EFS
|
33
|
+
@version = 63
|
34
|
+
end
|
35
|
+
@follow_symlinks = false
|
36
|
+
|
37
|
+
@restore_times = true
|
38
|
+
@restore_permissions = false
|
39
|
+
@restore_ownership = false
|
40
|
+
# BUG: need an extra field to support uid/gid's
|
41
|
+
@unix_uid = nil
|
42
|
+
@unix_gid = nil
|
43
|
+
@unix_perms = nil
|
44
|
+
# @posix_acl = nil
|
45
|
+
# @ntfs_acl = nil
|
46
|
+
@dirty = false
|
47
|
+
end
|
48
|
+
|
49
|
+
def check_name(name)
|
50
|
+
return unless name.start_with?('/')
|
51
|
+
raise ::Zip::EntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /"
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialize(*args)
|
55
|
+
name = args[1] || ''
|
56
|
+
check_name(name)
|
57
|
+
|
58
|
+
set_default_vars_values
|
59
|
+
@fstype = ::Zip::RUNNING_ON_WINDOWS ? ::Zip::FSTYPE_FAT : ::Zip::FSTYPE_UNIX
|
60
|
+
|
61
|
+
@zipfile = args[0] || ''
|
62
|
+
@name = name
|
63
|
+
@comment = args[2] || ''
|
64
|
+
@extra = args[3] || ''
|
65
|
+
@compressed_size = args[4] || 0
|
66
|
+
@crc = args[5] || 0
|
67
|
+
@compression_method = args[6] || ::Zip::Entry::DEFLATED
|
68
|
+
@size = args[7] || 0
|
69
|
+
@time = args[8] || ::Zip::DOSTime.now
|
70
|
+
|
71
|
+
@ftype = name_is_directory? ? :directory : :file
|
72
|
+
@extra = ::Zip::ExtraField.new(@extra.to_s) unless @extra.is_a?(::Zip::ExtraField)
|
73
|
+
end
|
74
|
+
|
75
|
+
def time
|
76
|
+
if @extra['UniversalTime']
|
77
|
+
@extra['UniversalTime'].mtime
|
78
|
+
elsif @extra['NTFS']
|
79
|
+
@extra['NTFS'].mtime
|
80
|
+
else
|
81
|
+
# Standard time field in central directory has local time
|
82
|
+
# under archive creator. Then, we can't get timezone.
|
83
|
+
@time
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
alias mtime time
|
88
|
+
|
89
|
+
def time=(value)
|
90
|
+
unless @extra.member?('UniversalTime') || @extra.member?('NTFS')
|
91
|
+
@extra.create('UniversalTime')
|
92
|
+
end
|
93
|
+
(@extra['UniversalTime'] || @extra['NTFS']).mtime = value
|
94
|
+
@time = value
|
95
|
+
end
|
96
|
+
|
97
|
+
def file_type_is?(type)
|
98
|
+
raise InternalError, "current filetype is unknown: #{inspect}" unless @ftype
|
99
|
+
@ftype == type
|
100
|
+
end
|
101
|
+
|
102
|
+
# Dynamic checkers
|
103
|
+
%w[directory file symlink].each do |k|
|
104
|
+
define_method "#{k}?" do
|
105
|
+
file_type_is?(k.to_sym)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def name_is_directory? #:nodoc:all
|
110
|
+
@name.end_with?('/')
|
111
|
+
end
|
112
|
+
|
113
|
+
# Is the name a relative path, free of `..` patterns that could lead to
|
114
|
+
# path traversal attacks? This does NOT handle symlinks; if the path
|
115
|
+
# contains symlinks, this check is NOT enough to guarantee safety.
|
116
|
+
def name_safe?
|
117
|
+
cleanpath = Pathname.new(@name).cleanpath
|
118
|
+
return false unless cleanpath.relative?
|
119
|
+
root = ::File::SEPARATOR
|
120
|
+
naive_expanded_path = ::File.join(root, cleanpath.to_s)
|
121
|
+
::File.absolute_path(cleanpath.to_s, root) == naive_expanded_path
|
122
|
+
end
|
123
|
+
|
124
|
+
def local_entry_offset #:nodoc:all
|
125
|
+
local_header_offset + @local_header_size
|
126
|
+
end
|
127
|
+
|
128
|
+
def name_size
|
129
|
+
@name ? @name.bytesize : 0
|
130
|
+
end
|
131
|
+
|
132
|
+
def extra_size
|
133
|
+
@extra ? @extra.local_size : 0
|
134
|
+
end
|
135
|
+
|
136
|
+
def comment_size
|
137
|
+
@comment ? @comment.bytesize : 0
|
138
|
+
end
|
139
|
+
|
140
|
+
def calculate_local_header_size #:nodoc:all
|
141
|
+
LOCAL_ENTRY_STATIC_HEADER_LENGTH + name_size + extra_size
|
142
|
+
end
|
143
|
+
|
144
|
+
# check before rewriting an entry (after file sizes are known)
|
145
|
+
# that we didn't change the header size (and thus clobber file data or something)
|
146
|
+
def verify_local_header_size!
|
147
|
+
return if @local_header_size.nil?
|
148
|
+
new_size = calculate_local_header_size
|
149
|
+
raise Error, "local header size changed (#{@local_header_size} -> #{new_size})" if @local_header_size != new_size
|
150
|
+
end
|
151
|
+
|
152
|
+
def cdir_header_size #:nodoc:all
|
153
|
+
CDIR_ENTRY_STATIC_HEADER_LENGTH + name_size +
|
154
|
+
(@extra ? @extra.c_dir_size : 0) + comment_size
|
155
|
+
end
|
156
|
+
|
157
|
+
def next_header_offset #:nodoc:all
|
158
|
+
local_entry_offset + compressed_size + data_descriptor_size
|
159
|
+
end
|
160
|
+
|
161
|
+
# Extracts entry to file dest_path (defaults to @name).
|
162
|
+
# NB: The caller is responsible for making sure dest_path is safe, if it
|
163
|
+
# is passed.
|
164
|
+
def extract(dest_path = nil, &block)
|
165
|
+
if dest_path.nil? && !name_safe?
|
166
|
+
puts "WARNING: skipped #{@name} as unsafe"
|
167
|
+
return self
|
168
|
+
end
|
169
|
+
|
170
|
+
dest_path ||= @name
|
171
|
+
block ||= proc { ::Zip.on_exists_proc }
|
172
|
+
|
173
|
+
if directory? || file? || symlink?
|
174
|
+
__send__("create_#{@ftype}", dest_path, &block)
|
175
|
+
else
|
176
|
+
raise "unknown file type #{inspect}"
|
177
|
+
end
|
178
|
+
|
179
|
+
self
|
180
|
+
end
|
181
|
+
|
182
|
+
def to_s
|
183
|
+
@name
|
184
|
+
end
|
185
|
+
|
186
|
+
class << self
|
187
|
+
def read_zip_short(io) # :nodoc:
|
188
|
+
io.read(2).unpack('v')[0]
|
189
|
+
end
|
190
|
+
|
191
|
+
def read_zip_long(io) # :nodoc:
|
192
|
+
io.read(4).unpack('V')[0]
|
193
|
+
end
|
194
|
+
|
195
|
+
def read_zip_64_long(io) # :nodoc:
|
196
|
+
io.read(8).unpack('Q<')[0]
|
197
|
+
end
|
198
|
+
|
199
|
+
def read_c_dir_entry(io) #:nodoc:all
|
200
|
+
path = if io.respond_to?(:path)
|
201
|
+
io.path
|
202
|
+
else
|
203
|
+
io
|
204
|
+
end
|
205
|
+
entry = new(path)
|
206
|
+
entry.read_c_dir_entry(io)
|
207
|
+
entry
|
208
|
+
rescue Error
|
209
|
+
nil
|
210
|
+
end
|
211
|
+
|
212
|
+
def read_local_entry(io)
|
213
|
+
entry = new(io)
|
214
|
+
entry.read_local_entry(io)
|
215
|
+
entry
|
216
|
+
rescue Error
|
217
|
+
nil
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
public
|
222
|
+
|
223
|
+
def unpack_local_entry(buf)
|
224
|
+
@header_signature,
|
225
|
+
@version,
|
226
|
+
@fstype,
|
227
|
+
@gp_flags,
|
228
|
+
@compression_method,
|
229
|
+
@last_mod_time,
|
230
|
+
@last_mod_date,
|
231
|
+
@crc,
|
232
|
+
@compressed_size,
|
233
|
+
@size,
|
234
|
+
@name_length,
|
235
|
+
@extra_length = buf.unpack('VCCvvvvVVVvv')
|
236
|
+
end
|
237
|
+
|
238
|
+
def read_local_entry(io) #:nodoc:all
|
239
|
+
@local_header_offset = io.tell
|
240
|
+
|
241
|
+
static_sized_fields_buf = io.read(::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH) || ''
|
242
|
+
|
243
|
+
unless static_sized_fields_buf.bytesize == ::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH
|
244
|
+
raise Error, 'Premature end of file. Not enough data for zip entry local header'
|
245
|
+
end
|
246
|
+
|
247
|
+
unpack_local_entry(static_sized_fields_buf)
|
248
|
+
|
249
|
+
unless @header_signature == ::Zip::LOCAL_ENTRY_SIGNATURE
|
250
|
+
raise ::Zip::Error, "Zip local header magic not found at location '#{local_header_offset}'"
|
251
|
+
end
|
252
|
+
set_time(@last_mod_date, @last_mod_time)
|
253
|
+
|
254
|
+
@name = io.read(@name_length)
|
255
|
+
extra = io.read(@extra_length)
|
256
|
+
|
257
|
+
@name.tr!('\\', '/')
|
258
|
+
if ::Zip.force_entry_names_encoding
|
259
|
+
@name.force_encoding(::Zip.force_entry_names_encoding)
|
260
|
+
end
|
261
|
+
|
262
|
+
if extra && extra.bytesize != @extra_length
|
263
|
+
raise ::Zip::Error, 'Truncated local zip entry header'
|
264
|
+
else
|
265
|
+
if @extra.is_a?(::Zip::ExtraField)
|
266
|
+
@extra.merge(extra) if extra
|
267
|
+
else
|
268
|
+
@extra = ::Zip::ExtraField.new(extra)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
parse_zip64_extra(true)
|
272
|
+
@local_header_size = calculate_local_header_size
|
273
|
+
end
|
274
|
+
|
275
|
+
def pack_local_entry
|
276
|
+
zip64 = @extra['Zip64']
|
277
|
+
[::Zip::LOCAL_ENTRY_SIGNATURE,
|
278
|
+
@version_needed_to_extract, # version needed to extract
|
279
|
+
@gp_flags, # @gp_flags
|
280
|
+
@compression_method,
|
281
|
+
@time.to_binary_dos_time, # @last_mod_time
|
282
|
+
@time.to_binary_dos_date, # @last_mod_date
|
283
|
+
@crc,
|
284
|
+
zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
|
285
|
+
zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
|
286
|
+
name_size,
|
287
|
+
@extra ? @extra.local_size : 0].pack('VvvvvvVVVvv')
|
288
|
+
end
|
289
|
+
|
290
|
+
def write_local_entry(io, rewrite = false) #:nodoc:all
|
291
|
+
prep_zip64_extra(true)
|
292
|
+
verify_local_header_size! if rewrite
|
293
|
+
@local_header_offset = io.tell
|
294
|
+
|
295
|
+
io << pack_local_entry
|
296
|
+
|
297
|
+
io << @name
|
298
|
+
io << @extra.to_local_bin if @extra
|
299
|
+
@local_header_size = io.tell - @local_header_offset
|
300
|
+
end
|
301
|
+
|
302
|
+
def unpack_c_dir_entry(buf)
|
303
|
+
@header_signature,
|
304
|
+
@version, # version of encoding software
|
305
|
+
@fstype, # filesystem type
|
306
|
+
@version_needed_to_extract,
|
307
|
+
@gp_flags,
|
308
|
+
@compression_method,
|
309
|
+
@last_mod_time,
|
310
|
+
@last_mod_date,
|
311
|
+
@crc,
|
312
|
+
@compressed_size,
|
313
|
+
@size,
|
314
|
+
@name_length,
|
315
|
+
@extra_length,
|
316
|
+
@comment_length,
|
317
|
+
_, # diskNumberStart
|
318
|
+
@internal_file_attributes,
|
319
|
+
@external_file_attributes,
|
320
|
+
@local_header_offset,
|
321
|
+
@name,
|
322
|
+
@extra,
|
323
|
+
@comment = buf.unpack('VCCvvvvvVVVvvvvvVV')
|
324
|
+
end
|
325
|
+
|
326
|
+
def set_ftype_from_c_dir_entry
|
327
|
+
@ftype = case @fstype
|
328
|
+
when ::Zip::FSTYPE_UNIX
|
329
|
+
@unix_perms = (@external_file_attributes >> 16) & 0o7777
|
330
|
+
case (@external_file_attributes >> 28)
|
331
|
+
when ::Zip::FILE_TYPE_DIR
|
332
|
+
:directory
|
333
|
+
when ::Zip::FILE_TYPE_FILE
|
334
|
+
:file
|
335
|
+
when ::Zip::FILE_TYPE_SYMLINK
|
336
|
+
:symlink
|
337
|
+
else
|
338
|
+
# best case guess for whether it is a file or not
|
339
|
+
# Otherwise this would be set to unknown and that entry would never be able to extracted
|
340
|
+
if name_is_directory?
|
341
|
+
:directory
|
342
|
+
else
|
343
|
+
:file
|
344
|
+
end
|
345
|
+
end
|
346
|
+
else
|
347
|
+
if name_is_directory?
|
348
|
+
:directory
|
349
|
+
else
|
350
|
+
:file
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
def check_c_dir_entry_static_header_length(buf)
|
356
|
+
return if buf.bytesize == ::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH
|
357
|
+
raise Error, 'Premature end of file. Not enough data for zip cdir entry header'
|
358
|
+
end
|
359
|
+
|
360
|
+
def check_c_dir_entry_signature
|
361
|
+
return if header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
|
362
|
+
raise Error, "Zip local header magic not found at location '#{local_header_offset}'"
|
363
|
+
end
|
364
|
+
|
365
|
+
def check_c_dir_entry_comment_size
|
366
|
+
return if @comment && @comment.bytesize == @comment_length
|
367
|
+
raise ::Zip::Error, 'Truncated cdir zip entry header'
|
368
|
+
end
|
369
|
+
|
370
|
+
def read_c_dir_extra_field(io)
|
371
|
+
if @extra.is_a?(::Zip::ExtraField)
|
372
|
+
@extra.merge(io.read(@extra_length))
|
373
|
+
else
|
374
|
+
@extra = ::Zip::ExtraField.new(io.read(@extra_length))
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
def read_c_dir_entry(io) #:nodoc:all
|
379
|
+
static_sized_fields_buf = io.read(::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH)
|
380
|
+
check_c_dir_entry_static_header_length(static_sized_fields_buf)
|
381
|
+
unpack_c_dir_entry(static_sized_fields_buf)
|
382
|
+
check_c_dir_entry_signature
|
383
|
+
set_time(@last_mod_date, @last_mod_time)
|
384
|
+
@name = io.read(@name_length)
|
385
|
+
if ::Zip.force_entry_names_encoding
|
386
|
+
@name.force_encoding(::Zip.force_entry_names_encoding)
|
387
|
+
end
|
388
|
+
read_c_dir_extra_field(io)
|
389
|
+
@comment = io.read(@comment_length)
|
390
|
+
check_c_dir_entry_comment_size
|
391
|
+
set_ftype_from_c_dir_entry
|
392
|
+
parse_zip64_extra(false)
|
393
|
+
end
|
394
|
+
|
395
|
+
def file_stat(path) # :nodoc:
|
396
|
+
if @follow_symlinks
|
397
|
+
::File.stat(path)
|
398
|
+
else
|
399
|
+
::File.lstat(path)
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
def get_extra_attributes_from_path(path) # :nodoc:
|
404
|
+
return if Zip::RUNNING_ON_WINDOWS
|
405
|
+
stat = file_stat(path)
|
406
|
+
@unix_uid = stat.uid
|
407
|
+
@unix_gid = stat.gid
|
408
|
+
@unix_perms = stat.mode & 0o7777
|
409
|
+
end
|
410
|
+
|
411
|
+
def set_unix_permissions_on_path(dest_path)
|
412
|
+
# BUG: does not update timestamps into account
|
413
|
+
# ignore setuid/setgid bits by default. honor if @restore_ownership
|
414
|
+
unix_perms_mask = 0o1777
|
415
|
+
unix_perms_mask = 0o7777 if @restore_ownership
|
416
|
+
::FileUtils.chmod(@unix_perms & unix_perms_mask, dest_path) if @restore_permissions && @unix_perms
|
417
|
+
::FileUtils.chown(@unix_uid, @unix_gid, dest_path) if @restore_ownership && @unix_uid && @unix_gid && ::Process.egid == 0
|
418
|
+
# File::utimes()
|
419
|
+
end
|
420
|
+
|
421
|
+
def set_extra_attributes_on_path(dest_path) # :nodoc:
|
422
|
+
return unless file? || directory?
|
423
|
+
|
424
|
+
case @fstype
|
425
|
+
when ::Zip::FSTYPE_UNIX
|
426
|
+
set_unix_permissions_on_path(dest_path)
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
def pack_c_dir_entry
|
431
|
+
zip64 = @extra['Zip64']
|
432
|
+
[
|
433
|
+
@header_signature,
|
434
|
+
@version, # version of encoding software
|
435
|
+
@fstype, # filesystem type
|
436
|
+
@version_needed_to_extract, # @versionNeededToExtract
|
437
|
+
@gp_flags, # @gp_flags
|
438
|
+
@compression_method,
|
439
|
+
@time.to_binary_dos_time, # @last_mod_time
|
440
|
+
@time.to_binary_dos_date, # @last_mod_date
|
441
|
+
@crc,
|
442
|
+
zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
|
443
|
+
zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
|
444
|
+
name_size,
|
445
|
+
@extra ? @extra.c_dir_size : 0,
|
446
|
+
comment_size,
|
447
|
+
zip64 && zip64.disk_start_number ? 0xFFFF : 0, # disk number start
|
448
|
+
@internal_file_attributes, # file type (binary=0, text=1)
|
449
|
+
@external_file_attributes, # native filesystem attributes
|
450
|
+
zip64 && zip64.relative_header_offset ? 0xFFFFFFFF : @local_header_offset,
|
451
|
+
@name,
|
452
|
+
@extra,
|
453
|
+
@comment
|
454
|
+
].pack('VCCvvvvvVVVvvvvvVV')
|
455
|
+
end
|
456
|
+
|
457
|
+
def write_c_dir_entry(io) #:nodoc:all
|
458
|
+
prep_zip64_extra(false)
|
459
|
+
case @fstype
|
460
|
+
when ::Zip::FSTYPE_UNIX
|
461
|
+
ft = case @ftype
|
462
|
+
when :file
|
463
|
+
@unix_perms ||= 0o644
|
464
|
+
::Zip::FILE_TYPE_FILE
|
465
|
+
when :directory
|
466
|
+
@unix_perms ||= 0o755
|
467
|
+
::Zip::FILE_TYPE_DIR
|
468
|
+
when :symlink
|
469
|
+
@unix_perms ||= 0o755
|
470
|
+
::Zip::FILE_TYPE_SYMLINK
|
471
|
+
end
|
472
|
+
|
473
|
+
unless ft.nil?
|
474
|
+
@external_file_attributes = (ft << 12 | (@unix_perms & 0o7777)) << 16
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
io << pack_c_dir_entry
|
479
|
+
|
480
|
+
io << @name
|
481
|
+
io << (@extra ? @extra.to_c_dir_bin : '')
|
482
|
+
io << @comment
|
483
|
+
end
|
484
|
+
|
485
|
+
def ==(other)
|
486
|
+
return false unless other.class == self.class
|
487
|
+
# Compares contents of local entry and exposed fields
|
488
|
+
keys_equal = %w[compression_method crc compressed_size size name extra filepath].all? do |k|
|
489
|
+
other.__send__(k.to_sym) == __send__(k.to_sym)
|
490
|
+
end
|
491
|
+
keys_equal && time.dos_equals(other.time)
|
492
|
+
end
|
493
|
+
|
494
|
+
def <=>(other)
|
495
|
+
to_s <=> other.to_s
|
496
|
+
end
|
497
|
+
|
498
|
+
# Returns an IO like object for the given ZipEntry.
|
499
|
+
# Warning: may behave weird with symlinks.
|
500
|
+
def get_input_stream(&block)
|
501
|
+
if @ftype == :directory
|
502
|
+
yield ::Zip::NullInputStream if block_given?
|
503
|
+
::Zip::NullInputStream
|
504
|
+
elsif @filepath
|
505
|
+
case @ftype
|
506
|
+
when :file
|
507
|
+
::File.open(@filepath, 'rb', &block)
|
508
|
+
when :symlink
|
509
|
+
linkpath = ::File.readlink(@filepath)
|
510
|
+
stringio = ::StringIO.new(linkpath)
|
511
|
+
yield(stringio) if block_given?
|
512
|
+
stringio
|
513
|
+
else
|
514
|
+
raise "unknown @file_type #{@ftype}"
|
515
|
+
end
|
516
|
+
else
|
517
|
+
zis = ::Zip::InputStream.new(@zipfile, local_header_offset)
|
518
|
+
zis.instance_variable_set(:@complete_entry, self)
|
519
|
+
zis.get_next_entry
|
520
|
+
if block_given?
|
521
|
+
begin
|
522
|
+
yield(zis)
|
523
|
+
ensure
|
524
|
+
zis.close
|
525
|
+
end
|
526
|
+
else
|
527
|
+
zis
|
528
|
+
end
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
532
|
+
def gather_fileinfo_from_srcpath(src_path) # :nodoc:
|
533
|
+
stat = file_stat(src_path)
|
534
|
+
@ftype = case stat.ftype
|
535
|
+
when 'file'
|
536
|
+
if name_is_directory?
|
537
|
+
raise ArgumentError,
|
538
|
+
"entry name '#{newEntry}' indicates directory entry, but " \
|
539
|
+
"'#{src_path}' is not a directory"
|
540
|
+
end
|
541
|
+
:file
|
542
|
+
when 'directory'
|
543
|
+
@name += '/' unless name_is_directory?
|
544
|
+
:directory
|
545
|
+
when 'link'
|
546
|
+
if name_is_directory?
|
547
|
+
raise ArgumentError,
|
548
|
+
"entry name '#{newEntry}' indicates directory entry, but " \
|
549
|
+
"'#{src_path}' is not a directory"
|
550
|
+
end
|
551
|
+
:symlink
|
552
|
+
else
|
553
|
+
raise "unknown file type: #{src_path.inspect} #{stat.inspect}"
|
554
|
+
end
|
555
|
+
|
556
|
+
@filepath = src_path
|
557
|
+
get_extra_attributes_from_path(@filepath)
|
558
|
+
end
|
559
|
+
|
560
|
+
def write_to_zip_output_stream(zip_output_stream) #:nodoc:all
|
561
|
+
if @ftype == :directory
|
562
|
+
zip_output_stream.put_next_entry(self, nil, nil, ::Zip::Entry::STORED)
|
563
|
+
elsif @filepath
|
564
|
+
zip_output_stream.put_next_entry(self, nil, nil, compression_method || ::Zip::Entry::DEFLATED)
|
565
|
+
get_input_stream { |is| ::Zip::IOExtras.copy_stream(zip_output_stream, is) }
|
566
|
+
else
|
567
|
+
zip_output_stream.copy_raw_entry(self)
|
568
|
+
end
|
569
|
+
end
|
570
|
+
|
571
|
+
def parent_as_string
|
572
|
+
entry_name = name.chomp('/')
|
573
|
+
slash_index = entry_name.rindex('/')
|
574
|
+
slash_index ? entry_name.slice(0, slash_index + 1) : nil
|
575
|
+
end
|
576
|
+
|
577
|
+
def get_raw_input_stream(&block)
|
578
|
+
if @zipfile.respond_to?(:seek) && @zipfile.respond_to?(:read)
|
579
|
+
yield @zipfile
|
580
|
+
else
|
581
|
+
::File.open(@zipfile, 'rb', &block)
|
582
|
+
end
|
583
|
+
end
|
584
|
+
|
585
|
+
def clean_up
|
586
|
+
# By default, do nothing
|
587
|
+
end
|
588
|
+
|
589
|
+
private
|
590
|
+
|
591
|
+
def set_time(binary_dos_date, binary_dos_time)
|
592
|
+
@time = ::Zip::DOSTime.parse_binary_dos_format(binary_dos_date, binary_dos_time)
|
593
|
+
rescue ArgumentError
|
594
|
+
warn 'Invalid date/time in zip entry' if ::Zip.warn_invalid_date
|
595
|
+
end
|
596
|
+
|
597
|
+
def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exists_proc })
|
598
|
+
if ::File.exist?(dest_path) && !yield(self, dest_path)
|
599
|
+
raise ::Zip::DestinationFileExistsError,
|
600
|
+
"Destination '#{dest_path}' already exists"
|
601
|
+
end
|
602
|
+
::File.open(dest_path, 'wb') do |os|
|
603
|
+
get_input_stream do |is|
|
604
|
+
set_extra_attributes_on_path(dest_path)
|
605
|
+
|
606
|
+
bytes_written = 0
|
607
|
+
warned = false
|
608
|
+
buf = ''.dup
|
609
|
+
while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf))
|
610
|
+
os << buf
|
611
|
+
bytes_written += buf.bytesize
|
612
|
+
if bytes_written > size && !warned
|
613
|
+
message = "Entry #{name} should be #{size}B but is larger when inflated"
|
614
|
+
if ::Zip.validate_entry_sizes
|
615
|
+
raise ::Zip::EntrySizeError, message
|
616
|
+
else
|
617
|
+
puts "WARNING: #{message}"
|
618
|
+
warned = true
|
619
|
+
end
|
620
|
+
end
|
621
|
+
end
|
622
|
+
end
|
623
|
+
end
|
624
|
+
end
|
625
|
+
|
626
|
+
def create_directory(dest_path)
|
627
|
+
return if ::File.directory?(dest_path)
|
628
|
+
if ::File.exist?(dest_path)
|
629
|
+
if block_given? && yield(self, dest_path)
|
630
|
+
::FileUtils.rm_f dest_path
|
631
|
+
else
|
632
|
+
raise ::Zip::DestinationFileExistsError,
|
633
|
+
"Cannot create directory '#{dest_path}'. " \
|
634
|
+
'A file already exists with that name'
|
635
|
+
end
|
636
|
+
end
|
637
|
+
::FileUtils.mkdir_p(dest_path)
|
638
|
+
set_extra_attributes_on_path(dest_path)
|
639
|
+
end
|
640
|
+
|
641
|
+
# BUG: create_symlink() does not use &block
|
642
|
+
def create_symlink(dest_path)
|
643
|
+
# TODO: Symlinks pose security challenges. Symlink support temporarily
|
644
|
+
# removed in view of https://github.com/rubyzip/rubyzip/issues/369 .
|
645
|
+
puts "WARNING: skipped symlink #{dest_path}"
|
646
|
+
end
|
647
|
+
|
648
|
+
# apply missing data from the zip64 extra information field, if present
|
649
|
+
# (required when file sizes exceed 2**32, but can be used for all files)
|
650
|
+
def parse_zip64_extra(for_local_header) #:nodoc:all
|
651
|
+
return if @extra['Zip64'].nil?
|
652
|
+
if for_local_header
|
653
|
+
@size, @compressed_size = @extra['Zip64'].parse(@size, @compressed_size)
|
654
|
+
else
|
655
|
+
@size, @compressed_size, @local_header_offset = @extra['Zip64'].parse(@size, @compressed_size, @local_header_offset)
|
656
|
+
end
|
657
|
+
end
|
658
|
+
|
659
|
+
def data_descriptor_size
|
660
|
+
(@gp_flags & 0x0008) > 0 ? 16 : 0
|
661
|
+
end
|
662
|
+
|
663
|
+
# create a zip64 extra information field if we need one
|
664
|
+
def prep_zip64_extra(for_local_header) #:nodoc:all
|
665
|
+
return unless ::Zip.write_zip64_support
|
666
|
+
need_zip64 = @size >= 0xFFFFFFFF || @compressed_size >= 0xFFFFFFFF
|
667
|
+
need_zip64 ||= @local_header_offset >= 0xFFFFFFFF unless for_local_header
|
668
|
+
if need_zip64
|
669
|
+
@version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT_ZIP64
|
670
|
+
@extra.delete('Zip64Placeholder')
|
671
|
+
zip64 = @extra.create('Zip64')
|
672
|
+
if for_local_header
|
673
|
+
# local header always includes size and compressed size
|
674
|
+
zip64.original_size = @size
|
675
|
+
zip64.compressed_size = @compressed_size
|
676
|
+
else
|
677
|
+
# central directory entry entries include whichever fields are necessary
|
678
|
+
zip64.original_size = @size if @size >= 0xFFFFFFFF
|
679
|
+
zip64.compressed_size = @compressed_size if @compressed_size >= 0xFFFFFFFF
|
680
|
+
zip64.relative_header_offset = @local_header_offset if @local_header_offset >= 0xFFFFFFFF
|
681
|
+
end
|
682
|
+
else
|
683
|
+
@extra.delete('Zip64')
|
684
|
+
|
685
|
+
# if this is a local header entry, create a placeholder
|
686
|
+
# so we have room to write a zip64 extra field afterward
|
687
|
+
# (we won't know if it's needed until the file data is written)
|
688
|
+
if for_local_header
|
689
|
+
@extra.create('Zip64Placeholder')
|
690
|
+
else
|
691
|
+
@extra.delete('Zip64Placeholder')
|
692
|
+
end
|
693
|
+
end
|
694
|
+
end
|
695
|
+
end
|
696
|
+
end
|
697
|
+
|
698
|
+
# Copyright (C) 2002, 2003 Thomas Sondergaard
|
699
|
+
# rubyzip is free software; you can redistribute it and/or
|
700
|
+
# modify it under the terms of the ruby license.
|